/* =====================================================================
aether-medical / Ansicht: Patientenverwaltung
Warteliste, Belegung und Entlassung. Die Liste ist nach der vom
Mediziner gesetzten Dringlichkeit sortiert — nicht nach Wartezeit.
Wer als Nächstes drankommt, entscheidet niemand ausser dir.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen } = App;
const z = {
ordner: "warteliste", // warteliste | station | entlassen
station: "",
daten: null,
offen: null, // geöffnete Aufnahme
};
// Farbe der Dringlichkeitsstufen
const prioArt = { 1: "kritisch", 2: "warn", 3: "info", 4: "neutral" };
/* ---- Gerüst ---------------------------------------------------------- */
function reiterleiste(d) {
const eintraege = [
{ id: "warteliste", text: "Warteliste", zahl: d.zaehler.wartend },
{ id: "station", text: "Im Haus", zahl: d.zaehler.aufgenommen },
{ id: "entlassen", text: "Entlassen", zahl: d.zaehler.entlassenHeute },
];
return `
${eintraege.map((e) => `
${sicher(e.text)}${e.zahl}
`).join("")}
`;
}
/* ---- Übersicht -------------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
// Belegung der Stationen als Kachelreihe
const stationsKacheln = d.stationen.map((s) => `
${sicher(s.name)}
${s.frei}
frei von ${s.betten} · ${s.zimmer} Zimmer
`).join("");
behaelter.innerHTML = `
Patientenverwaltung
Angemeldet wird von Hand, aufgenommen wird von Hand, entlassen wird von Hand.
Das System führt nur die Liste.
${d.rechte.aufnehmen
? `
+ Patient anmelden ` : ""}
${stationsKacheln}
${reiterleiste(d)}
Alle Stationen
${d.stationen.map((s) =>
`
${sicher(s.name)} `).join("")}
${d.rechte.belegen
? `Zimmerübersicht ` : ""}
${z.ordner === "warteliste" ? "Wartende Patienten"
: z.ordner === "station" ? "Patienten im Haus" : "Zuletzt entlassen"}
${z.ordner === "warteliste"
? "Sortiert nach eingetragener Dringlichkeit, nicht nach Wartezeit"
: d.liste.length + " Einträge"}
${d.liste.length ? d.liste.map(zeile).join("")
: `
${z.ordner === "warteliste"
? "Niemand wartet." : "Keine Einträge."}
`}
`;
verdrahteUebersicht(behaelter);
}
function zeile(a) {
const entlassen = a.status === "entlassen";
return `
${sicher(a.name)}
geb. ${sicher(a.geburt)}
${sicher(String(a.grund).slice(0, 90))}
${a.hinweis ? `
⚠ ` : ""}
${a.allergien ? `
A ` : ""}
${a.blutgruppe ? `
${sicher(a.blutgruppe)} ` : ""}
${a.aufIntensiv ? `
Intensiv ` : ""}
${a.zimmer ? `
${sicher(a.zimmer)} `
: a.stationName ? `
${sicher(a.stationName)} ` : ""}
${sicher(a.dringlichkeit)}
${entlassen
? zeit(a.entlassen_am)
: (z.ordner === "warteliste" ? "wartet " : "seit ") + minuten(a.wartet)}
`;
}
function verdrahteUebersicht(behaelter) {
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
el.addEventListener("click", () => {
z.ordner = el.dataset.reiter;
rendern(behaelter);
});
});
behaelter.querySelector("[data-station]").addEventListener("change", (e) => {
z.station = e.target.value;
rendern(behaelter);
});
behaelter.querySelectorAll("[data-oeffne]").forEach((el) => {
el.addEventListener("click", () => oeffne(behaelter, +el.dataset.oeffne));
});
const anmelden = behaelter.querySelector("[data-anmelden]");
if (anmelden) anmelden.addEventListener("click", () => anmeldeFenster(behaelter));
const zimmer = behaelter.querySelector("[data-zimmer]");
if (zimmer) zimmer.addEventListener("click", () => zimmerUebersicht(behaelter));
}
/* ---- Einzelansicht ------------------------------------------------------ */
function einzelAnsicht(behaelter, d) {
const a = d.aufnahme, s = d.stamm;
behaelter.innerHTML = `
${sicher(a.name)}
geb. ${sicher(a.geburt)} · ${sicher(a.dringlichkeit)}
${a.stationName ? " · " + sicher(a.stationName) : ""}
${a.zimmer ? " · Zimmer " + sicher(a.zimmer)
+ (a.bett ? " / Bett " + a.bett : "") : ""}
← Übersicht
${s.hinweis ? `
⚠
Warnhinweis — ${sicher(s.hinweis)}
` : ""}
${s.allergien ? `
💊
Allergien — ${sicher(s.allergien)}
` : ""}
${s.lichtbild
? `
`
: `
Kein Lichtbild `}
Status
${sicher(statusName(a.status))}
Dringlichkeit
${sicher(a.dringlichkeit)}
Blutgruppe
${sicher(s.blutgruppe || "nicht bestimmt")}
Vorerkrankungen
${sicher(s.vorerkrankungen || "keine erfasst")}
Dauermedikation
${sicher(s.dauermedikation || "keine erfasst")}
Notfallkontakt
${sicher(s.notfallkontakt || "keiner hinterlegt")}
Patientenakte öffnen
${a.status === "wartend" && d.rechte.aufnehmen
? `Aufnehmen ` : ""}
${a.status === "aufgenommen" && d.rechte.verlegen
? `Verlegen ` : ""}
${a.status !== "entlassen" && d.rechte.aufnehmen
? `Dringlichkeit ` : ""}
${a.status !== "entlassen" && d.rechte.entlassen
? `Entlassen ` : ""}
Vorgang
Grund der Vorstellung ${sicher(a.grund)}
Angemeldet ${zeit(a.aufgenommenAm)}
Angemeldet von ${sicher(a.aufgenommenVon)}
${a.entlassenAm ? `
Entlassen ${zeit(a.entlassenAm)}
Entlassen von ${sicher(a.entlassenVon)}
` : ""}
${a.entlassGrund ? `
Entlassgrund: ${sicher(a.entlassGrund)}
` : ""}
Behandlungen zu dieser Aufnahme
${d.behandlungen.length ? d.behandlungen.map((b) => `
${sicher(b.kennung)}
${sicher(b.diagnose || "Noch keine Diagnose eingetragen")}
${sicher(b.autor)} · ${zeit(b.erstellt_am)}
${sicher(b.status)}
`).join("")
: `
Noch nichts dokumentiert.
`}
`;
verdrahteEinzel(behaelter, d);
}
function statusName(status) {
return { wartend: "Wartet auf Aufnahme", aufgenommen: "Aufgenommen",
entlassen: "Entlassen" }[status] || status;
}
function verdrahteEinzel(behaelter, d) {
const a = d.aufnahme;
behaelter.querySelector("[data-zurueck]").addEventListener("click", () => {
z.offen = null;
rendern(behaelter);
});
behaelter.querySelector("[data-akte]").addEventListener("click", () => {
if (!App.zustand.module.some((m) => m.id === "mdt")) {
return meldung("Das MDT steht dir nicht zur Verfügung.", "fehler");
}
App.oeffneModul("mdt", { patient: a.patient });
});
const aufnehmen = behaelter.querySelector("[data-aufnehmen]");
if (aufnehmen) aufnehmen.addEventListener("click", () => zuweisungsFenster(behaelter, d, false));
const verlegen = behaelter.querySelector("[data-verlegen]");
if (verlegen) verlegen.addEventListener("click", () => zuweisungsFenster(behaelter, d, true));
const prio = behaelter.querySelector("[data-prio]");
if (prio) prio.addEventListener("click", () => prioFenster(behaelter, d));
const entlassen = behaelter.querySelector("[data-entlassen]");
if (entlassen) entlassen.addEventListener("click", () => entlassungsFenster(behaelter, d));
}
/* ---- Anmeldung ----------------------------------------------------------- */
function anmeldeFenster(behaelter) {
let gewaehlt = null;
bestaetigen({
titel: "Patient anmelden",
inhalt: `
Person suchen
Mindestens zwei Zeichen eingeben.
Grund der Vorstellung
Dringlichkeit
1 — Sofort (lebensbedrohlich)
2 — Dringend (zeitkritisch)
3 — Regulär
4 — Nachrangig
Diese Einstufung nimmst du vor. Das System
ändert sie nie — auch nicht, wenn jemand lange wartet.
`,
bestaetigenText: "Anmelden",
bestaetigt: async (el, zu) => {
if (!gewaehlt) return meldung("Bitte zuerst eine Person wählen.", "fehler");
try {
await aufruf("patienten", "anmelden", {
patient: gewaehlt.id,
grund: el.querySelector("[name=grund]").value.trim(),
prioritaet: +el.querySelector("[name=prioritaet]").value,
});
zu();
meldung("Patient auf der Warteliste.", "gut");
z.ordner = "warteliste";
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
nachOeffnen: (hg) => {
const suchfeld = hg.querySelector("[name=suche]");
const liste = hg.querySelector("[data-treffer]");
const formular = hg.querySelector("[data-formular]");
let warten = null;
suchfeld.addEventListener("input", () => {
clearTimeout(warten);
warten = setTimeout(async () => {
const begriff = suchfeld.value.trim();
if (begriff.length < 2) {
liste.innerHTML = `Mindestens zwei Zeichen eingeben.
`;
return;
}
try {
const antwort = await aufruf("patienten", "patientSuche", { begriff });
const treffer = antwort.treffer || [];
liste.innerHTML = treffer.length ? treffer.map((t) => `
${sicher(t.firstname)} ${sicher(t.lastname)}
geb. ${sicher(t.dob)}
${t.blutgruppe ? " · " + sicher(t.blutgruppe) : ""}
${t.hinweis ? `
⚠ ` : ""}
${t.laufend ? `
${sicher(t.laufend)} ` : ""}
`).join("")
: `Keine Person gefunden.
`;
liste.querySelectorAll("[data-person]").forEach((el) => {
el.addEventListener("click", () => {
const person = treffer.find((t) => t.id === +el.dataset.person);
if (person && person.laufend) {
return meldung("Für diesen Patienten läuft bereits eine Aufnahme.", "fehler");
}
gewaehlt = person;
liste.querySelectorAll("[data-person]")
.forEach((zeile) => zeile.classList.remove("gewaehlt"));
el.classList.add("gewaehlt");
formular.style.display = "";
});
});
} catch (f) { meldung(f.message, "fehler"); }
}, 260);
});
},
});
}
/* ---- Aufnahme und Verlegung ----------------------------------------------- */
async function zuweisungsFenster(behaelter, d, istVerlegung) {
const a = d.aufnahme;
let zimmerDaten;
try {
zimmerDaten = await aufruf("patienten", "freieZimmer", {});
} catch (f) { return meldung(f.message, "fehler"); }
// Zimmer nach Station gruppieren
const nachStation = {};
zimmerDaten.zimmer.forEach((zi) => {
(nachStation[zi.station] = nachStation[zi.station] || {
name: zi.stationName, zimmer: [],
}).zimmer.push(zi);
});
let gewaehltesZimmer = null;
let gewaehlteStation = null;
bestaetigen({
titel: istVerlegung ? "Patient verlegen" : "Patient aufnehmen",
inhalt: `
${sicher(a.name)} — ${sicher(a.grund)}
${istVerlegung
? `Grund der Verlegung
`
: `Vermerk (freiwillig)
`}
Station und Zimmer
${Object.keys(nachStation).map((kennung) => {
const st = nachStation[kennung];
return `
${sicher(st.name)}
ohne Zimmer
${st.zimmer.map((zi) => `
${sicher(zi.kennung)}
${zi.frei}/${zi.betten} frei
${zi.status !== "frei"
? `${sicher(zi.status)} ` : ""}
`).join("")}
`;
}).join("")}
Das System zeigt nur, was frei ist. Welches Zimmer
zu diesem Patienten passt, entscheidest du.
`,
bestaetigenText: istVerlegung ? "Verlegen" : "Aufnehmen",
bestaetigt: async (el, zu) => {
if (!gewaehlteStation) {
return meldung("Bitte eine Station wählen.", "fehler");
}
if (istVerlegung) {
const grund = el.querySelector("[name=grund]").value.trim();
if (grund.length < 3) return meldung("Bitte den Grund angeben.", "fehler");
}
try {
const antwort = await aufruf("patienten", istVerlegung ? "verlegen" : "aufnehmen", {
id: a.id,
station: gewaehlteStation,
zimmer: gewaehltesZimmer,
grund: istVerlegung ? el.querySelector("[name=grund]").value.trim() : null,
vermerk: istVerlegung ? null : el.querySelector("[name=vermerk]").value.trim(),
});
zu();
meldung(istVerlegung ? "Patient verlegt."
: "Patient aufgenommen." + (antwort.bett ? " Bett " + antwort.bett + "." : ""), "gut");
oeffne(behaelter, a.id);
} catch (f) { meldung(f.message, "fehler"); }
},
nachOeffnen: (hg) => {
const alleKnoepfe = () => hg.querySelectorAll("[data-zimmer], [data-nurstation]");
hg.querySelectorAll("[data-zimmer]").forEach((el) => {
el.addEventListener("click", () => {
gewaehltesZimmer = +el.dataset.zimmer;
gewaehlteStation = el.dataset.station;
alleKnoepfe().forEach((k) => k.classList.remove("gewaehlt"));
el.classList.add("gewaehlt");
});
});
hg.querySelectorAll("[data-nurstation]").forEach((el) => {
el.addEventListener("click", () => {
gewaehltesZimmer = null;
gewaehlteStation = el.dataset.nurstation;
alleKnoepfe().forEach((k) => k.classList.remove("gewaehlt"));
el.classList.add("gewaehlt");
});
});
},
});
}
/* ---- Dringlichkeit --------------------------------------------------------- */
function prioFenster(behaelter, d) {
const a = d.aufnahme;
bestaetigen({
titel: "Dringlichkeit ändern",
inhalt: `
Aktuell: ${sicher(a.dringlichkeit)}
Neue Einstufung
${[1, 2, 3, 4].map((stufe) => `
${stufe} — ${sicher(d.dringlichkeit[stufe].name)}
(${sicher(d.dringlichkeit[stufe].hinweis)}) `).join("")}
Begründung (Pflicht)
`,
bestaetigenText: "Einstufung setzen",
bestaetigt: async (el, zu) => {
const grund = el.querySelector("[name=grund]").value.trim();
if (grund.length < 3) return meldung("Bitte eine Begründung angeben.", "fehler");
try {
await aufruf("patienten", "prioritaet", {
id: a.id,
prioritaet: +el.querySelector("[name=prioritaet]").value,
grund,
});
zu();
meldung("Einstufung geändert.", "gut");
oeffne(behaelter, a.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
}
/* ---- Entlassung ------------------------------------------------------------ */
function entlassungsFenster(behaelter, d) {
const a = d.aufnahme;
const offeneDoku = d.behandlungen.filter((b) => b.status === "laufend").length;
bestaetigen({
titel: "Patient entlassen",
gefahr: true,
inhalt: `
${sicher(a.name)} wird aus der Klinik entlassen.
Ein belegtes Bett wird dabei frei.
${offeneDoku > 0 ? `
📄
${offeneDoku} Behandlung(en) noch nicht abgeschlossen.
Sie bleiben offen — das System schließt nichts für dich ab.
Die Entlassung ist trotzdem möglich.
` : ""}
Begründung / Entlassbrief (Pflicht)
`,
bestaetigenText: "Entlassen",
bestaetigt: async (el, zu) => {
const grund = el.querySelector("[name=grund]").value.trim();
if (grund.length < 5) return meldung("Bitte eine Begründung angeben.", "fehler");
try {
await aufruf("patienten", "entlassen", { id: a.id, grund });
zu();
meldung("Patient entlassen.", "gut");
z.offen = null;
z.ordner = "entlassen";
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
});
}
/* ---- Zimmerübersicht -------------------------------------------------------- */
async function zimmerUebersicht(behaelter) {
let d;
try {
d = await aufruf("patienten", "freieZimmer", {});
} catch (f) { return meldung(f.message, "fehler"); }
const nachStation = {};
d.zimmer.forEach((zi) => {
(nachStation[zi.station] = nachStation[zi.station] || {
name: zi.stationName, zimmer: [],
}).zimmer.push(zi);
});
const dialog = fenster({
titel: "Zimmerübersicht",
inhalt: `
Ein leer gewordenes Zimmer meldet sich als frei, setzt
seinen Status aber nicht selbst zurück — Reinigung und Freigabe bleiben
eine Entscheidung.
${Object.keys(nachStation).map((kennung) => {
const st = nachStation[kennung];
return `
${sicher(st.name)}
${st.zimmer.map((zi) => `
${sicher(zi.kennung)}
${zi.frei} von ${zi.betten} Betten frei
${sicher(zi.art)}
${zi.hinweis ? " · " + sicher(zi.hinweis) : ""}
0 ? "gut" : "warn")
: zi.status === "reinigung" ? "warn" : "kritisch"}">
${sicher(zi.status)}
Status ändern…
frei
Reinigung
gesperrt
`).join("")}
`;
}).join("")}`,
nachOeffnen: (hg) => {
hg.querySelectorAll("[data-zimmer]").forEach((el) => {
el.addEventListener("change", async () => {
if (!el.value) return;
try {
await aufruf("patienten", "zimmerStatus", {
id: +el.dataset.zimmer, status: el.value,
});
meldung("Zimmerstatus gesetzt.", "gut");
dialog.schliessen();
rendern(behaelter);
} catch (f) {
meldung(f.message, "fehler");
el.value = "";
}
});
});
},
});
}
/* ---- Laden ------------------------------------------------------------------- */
async function oeffne(behaelter, aufnahmeId) {
try {
const d = await aufruf("patienten", "oeffnen", { id: aufnahmeId });
z.offen = aufnahmeId;
einzelAnsicht(behaelter, d);
} catch (f) { meldung(f.message, "fehler"); }
}
async function rendern(behaelter) {
try {
z.daten = await aufruf("patienten", "uebersicht", {
ordner: z.ordner, station: z.station,
});
// Sprungziel aus einem Hinweis, z. B. „Patient wartet"
const ziel = App.zustand.modulZiel;
if (ziel && ziel.aufnahme) {
App.zustand.modulZiel = null;
return oeffne(behaelter, ziel.aufnahme);
}
if (z.offen) await oeffne(behaelter, z.offen);
else uebersicht(behaelter);
} catch (f) {
behaelter.innerHTML = `Patientenverwaltung
Konnte nicht geladen werden: ${sicher(f.message)}
`;
}
}
App.registriereAnsicht("patienten", {
rendern,
aktualisieren() {
// Nur die Übersicht auffrischen, nicht eine offene Einzelansicht stören
if (!z.offen && App.zustand.aktivesModul === "patienten") {
rendern(App.$("inhalt"));
}
},
});
})();