/* ===================================================================== aether-medical / Ansicht: Stationen und Zimmerbelegung Der Bettenspiegel ist eine Auskunft, keine Vorgabe. Er zeigt, was frei ist und wer wo liegt — welches Bett zu welchem Patienten passt, entscheidest du. Kein Zimmer wechselt hier seinen Status von selbst. ===================================================================== */ "use strict"; (() => { const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen } = App; const z = { reiter: "spiegel", // spiegel | ohnebett | verwaltung station: "", // Filter auf eine Station (Kennung) zeigeInaktive: false, // Abgeschaltete Stationen mitzeigen (nur Verwaltung) daten: null, }; // Farbe der Dringlichkeitsstufen — dieselbe Lesart wie in der Patientenverwaltung const prioArt = { 1: "kritisch", 2: "warn", 3: "info", 4: "neutral" }; /* ---- Kleine Helfer --------------------------------------------------- */ /** Marker-Art zum Zimmerstatus */ function statusArt(status, frei) { if (status === "gesperrt") return "kritisch"; if (status === "reinigung") return "warn"; return frei > 0 ? "gut" : "neutral"; } /** Anzeigename eines Zimmerstatus */ function statusName(status) { const treffer = (z.daten.zimmerstatus || []).find((s) => s.id === status); return treffer ? treffer.name : status; } /** Anzeigename einer Zimmerart */ function artName(art) { const treffer = (z.daten.zimmerarten || []).find((a) => a.id === art); return treffer ? treffer.name : art; } /** Alle Stationen, die der aktuelle Filter durchlässt */ function sichtbareStationen() { if (!z.station) return z.daten.stationen; return z.daten.stationen.filter((s) => s.kennung === z.station); } /* ---- Gerüst ----------------------------------------------------------- */ function reiterleiste(d) { const eintraege = [ { id: "spiegel", text: "Bettenspiegel", zahl: d.zaehler.frei }, { id: "ohnebett", text: "Ohne Bett", zahl: d.ohneBett.length }, ]; if (d.rechte.verwalten) { eintraege.push({ id: "verwaltung", text: "Verwaltung", zahl: d.zaehler.zimmer }); } return `
${eintraege.map((e) => ` `).join("")}
`; } function kennzahlen(d) { const zk = d.zaehler; const auslastung = zk.betten > 0 ? Math.round((zk.belegt / zk.betten) * 100) : 0; return `
Freie Betten
${zk.frei}
von ${zk.betten} Betten insgesamt
Belegt
${zk.belegt}
${auslastung} % ausgelastet
Nicht belegbar
${zk.gesperrt + zk.reinigung}
${zk.gesperrt} gesperrt · ${zk.reinigung} in Reinigung
Ohne Bett
${d.ohneBett.length}
aufgenommen, aber in keinem Zimmer
`; } /* ---- Bettenspiegel ------------------------------------------------------ */ /** Ein einzelnes Bett als Zelle */ function bettZelle(bett) { const b = bett.belegung; if (!b) { return `
${bett.nummer} frei
`; } return `
${bett.nummer} ${sicher(b.name)} ${b.warnhinweis ? `⚠` : ""} ${b.allergien ? `A` : ""} ${b.aufIntensiv ? `ITS` : ""} ${b.prioritaet ? ` ${b.prioritaet}` : ""}
`; } /** Eine Zimmerkachel */ function zimmerKachel(zi) { return `
${sicher(zi.kennung)} ${zi.status === "frei" ? zi.frei + " / " + zi.betten + " frei" : sicher(statusName(zi.status))}
${zi.bettenListe.map(bettZelle).join("")}
${zi.hinweis ? `
${sicher(zi.hinweis)}
` : ""}
`; } /** Eine Station mit allen ihren Zimmern */ function stationsKarte(s) { return `
${sicher(s.name)} ${s.etage ? ` · ${sicher(s.etage)}` : ""} ${s.aktiv ? "" : ` abgeschaltet`} ${s.belegt} von ${s.betten} Betten belegt · ${s.frei} frei
${s.zimmer.length ? `
${s.zimmer.map(zimmerKachel).join("")}
` : `
Für diese Station ist kein Zimmer angelegt.
`}
`; } function spiegelAnsicht(d) { const stationen = sichtbareStationen(); return stationen.length ? `
${stationen.map(stationsKarte).join("")}
` : `
Keine Station gefunden.
`; } /* ---- Patienten ohne Bett -------------------------------------------------- */ function ohneBettAnsicht(d) { return `
Aufgenommen, aber ohne Bett Das System legt niemanden von selbst hin
${d.ohneBett.length ? d.ohneBett.map((a) => `
${sicher(a.name)}
${sicher(String(a.grund || "").slice(0, 90))}
${a.warnhinweis ? `⚠` : ""} ${a.allergien ? `A` : ""} ${a.blutgruppe ? `${sicher(a.blutgruppe)}` : ""} ${a.stationName ? `${sicher(a.stationName)}` : ""} seit ${sicher(minuten(a.wartet))} ${d.rechte.belegen ? `` : ""}
`).join("") : `
Jeder aufgenommene Patient liegt in einem Bett.
`}
`; } /* ---- Verwaltung ------------------------------------------------------------ */ function verwaltungsAnsicht(d) { return `
ℹ
Stationen und Zimmer werden abgeschaltet oder gesperrt, nicht gelöscht. Nur ein Zimmer, in dem nie jemand gelegen hat, lässt sich noch entfernen — sonst verlöre der Belegungsverlauf seinen Ort.
${d.stationen.map((s) => `
${sicher(s.kennung)} ${sicher(s.name)} ${s.aktiv ? "" : ` abgeschaltet`}
${s.zimmer.length ? s.zimmer.map((zi) => `
${sicher(zi.kennung)}
${zi.betten} Bett(en) · ${sicher(artName(zi.art))}
${zi.belegt} belegt${ zi.hinweis ? " · " + sicher(zi.hinweis) : ""}
${sicher(statusName(zi.status))} ${zi.belegt === 0 ? `` : ""}
`).join("") : `
Noch kein Zimmer angelegt.
`}
`).join("")} `; } /* ---- Gesamtansicht ---------------------------------------------------------- */ function uebersicht(behaelter) { const d = z.daten; behaelter.innerHTML = `

Stationen und Belegung

Belegt und frei gegeben wird von Hand. Ein leer gewordenes Zimmer meldet sich, setzt seinen Status aber nicht selbst zurück — Reinigung und Freigabe bleiben eine Entscheidung.

${kennzahlen(d)} ${reiterleiste(d)} ${z.reiter === "spiegel" ? `
Klick auf ein Zimmer öffnet die Belegung.
` : ""} ${z.reiter === "spiegel" ? spiegelAnsicht(d) : z.reiter === "ohnebett" ? ohneBettAnsicht(d) : verwaltungsAnsicht(d)} `; verdrahteUebersicht(behaelter); } function verdrahteUebersicht(behaelter) { behaelter.querySelectorAll("[data-reiter]").forEach((el) => { el.addEventListener("click", () => { z.reiter = el.dataset.reiter; // Abgeschaltete Stationen gehören nur in die Verwaltung if (z.reiter !== "verwaltung") z.zeigeInaktive = false; rendern(behaelter); }); }); const filter = behaelter.querySelector("[data-stationsfilter]"); if (filter) { filter.addEventListener("change", (e) => { z.station = e.target.value; uebersicht(behaelter); }); } behaelter.querySelectorAll("[data-zimmer]").forEach((el) => { el.addEventListener("click", () => zimmerFenster(behaelter, +el.dataset.zimmer)); }); behaelter.querySelectorAll("[data-zuweisen]").forEach((el) => { el.addEventListener("click", () => { const aufnahme = z.daten.ohneBett.find((a) => a.id === +el.dataset.zuweisen); if (aufnahme) zuweisungsFenster(behaelter, aufnahme); }); }); /* --- Verwaltung --- */ const inaktive = behaelter.querySelector("[data-inaktive]"); if (inaktive) { inaktive.addEventListener("change", () => { z.zeigeInaktive = inaktive.checked; rendern(behaelter); }); } const neueStation = behaelter.querySelector("[data-neuestation]"); if (neueStation) { neueStation.addEventListener("click", () => stationsFenster(behaelter, null)); } behaelter.querySelectorAll("[data-station-bearbeiten]").forEach((el) => { el.addEventListener("click", () => { const station = z.daten.stationen.find((s) => s.id === +el.dataset.stationBearbeiten); if (station) stationsFenster(behaelter, station); }); }); behaelter.querySelectorAll("[data-station-aktiv]").forEach((el) => { el.addEventListener("click", () => { const station = z.daten.stationen.find((s) => s.id === +el.dataset.stationAktiv); if (station) aktivFenster(behaelter, station, el.dataset.ziel === "1"); }); }); behaelter.querySelectorAll("[data-neueszimmer]").forEach((el) => { el.addEventListener("click", () => zimmerBearbeitenFenster(behaelter, null, +el.dataset.neueszimmer)); }); behaelter.querySelectorAll("[data-zimmer-bearbeiten]").forEach((el) => { el.addEventListener("click", () => { const zimmer = findeZimmer(+el.dataset.zimmerBearbeiten); if (zimmer) zimmerBearbeitenFenster(behaelter, zimmer, zimmer.station); }); }); behaelter.querySelectorAll("[data-zimmer-entfernen]").forEach((el) => { el.addEventListener("click", () => { const zimmer = findeZimmer(+el.dataset.zimmerEntfernen); if (zimmer) entfernenFenster(behaelter, zimmer); }); }); } /** Sucht ein Zimmer über alle Stationen hinweg */ function findeZimmer(zimmerId) { for (const station of z.daten.stationen) { const treffer = station.zimmer.find((zi) => zi.id === zimmerId); if (treffer) return treffer; } return null; } /* ---- Zimmerfenster ---------------------------------------------------------- */ async function zimmerFenster(behaelter, zimmerId) { let d; try { d = await aufruf("station", "zimmer", { id: zimmerId }); } catch (f) { return meldung(f.message, "fehler"); } const zi = d.zimmer; const dialog = fenster({ titel: `Zimmer ${zi.kennung} · ${zi.stationName}`, inhalt: `
Art${sicher(artName(zi.art))}
Status${sicher(statusName(zi.status))}
Betten${zi.betten}
${zi.etage ? `
Etage${sicher(zi.etage)}
` : ""}
${zi.hinweis ? `
ℹ
${sicher(zi.hinweis)}
` : ""}
Belegung
${d.betten.map((bett) => { const b = bett.belegung; if (!b) { return `
Bett ${bett.nummer}
frei
`; } return `
Bett ${bett.nummer}
${sicher(b.name)} geb. ${sicher(b.geburt)}
liegt seit ${sicher(minuten(b.liegtSeit))} · belegt von ${sicher(b.belegtVon)}
${b.warnhinweis ? `⚠` : ""} ${b.allergien ? `A` : ""} ${b.blutgruppe ? `${sicher(b.blutgruppe)}` : ""} ${b.aufnahme ? `` : ""} ${d.rechte.belegen ? `` : ""}
`; }).join("")}
${d.rechte.belegen ? `
Ein leeres Zimmer bleibt so lange in Reinigung oder gesperrt, bis jemand es wieder freigibt. Das System tut das nie.
` : ""} ${d.verlauf.length ? `
Frühere Belegungen bleibt dauerhaft erhalten
${d.verlauf.map((v) => `
Bett ${v.bett}
${sicher(v.name)}
${zeit(v.belegt_am)} → ${zeit(v.frei_am)} · freigegeben von ${sicher(v.freiVon)}
`).join("")}
` : ""} `, nachOeffnen: (hg) => { // Sprung in die Patientenverwaltung hg.querySelectorAll("[data-akte]").forEach((el) => { el.addEventListener("click", () => { if (!App.zustand.module.some((m) => m.id === "patienten")) { return meldung("Die Patientenverwaltung steht dir nicht zur Verfügung.", "fehler"); } dialog.schliessen(); App.oeffneModul("patienten", { aufnahme: +el.dataset.akte }); }); }); hg.querySelectorAll("[data-freigeben]").forEach((el) => { el.addEventListener("click", () => { dialog.schliessen(); freigabeFenster(behaelter, { id: +el.dataset.freigeben, name: el.dataset.name, bett: el.dataset.bett, zimmer: zi.kennung, }); }); }); const setzen = hg.querySelector("[data-statussetzen]"); if (setzen) { setzen.addEventListener("click", async () => { const status = hg.querySelector("[data-neuerstatus]").value; const hinweis = hg.querySelector("[data-statushinweis]").value.trim(); try { await aufruf("station", "zimmerStatus", { id: zi.id, status, hinweis }); dialog.schliessen(); meldung("Zimmerstatus gesetzt.", "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }); } }, }); } /* ---- Bett freigeben ---------------------------------------------------------- */ function freigabeFenster(behaelter, belegung) { bestaetigen({ titel: "Bett freigeben", inhalt: `

${sicher(belegung.name)} liegt in Zimmer ${sicher(belegung.zimmer)}, Bett ${sicher(belegung.bett)}.

🧍
Das ist keine Entlassung. Der Patient bleibt aufgenommen und steht danach ohne Bett auf der Station. Entlassen wird ausschließlich über die Patientenverwaltung.
`, bestaetigenText: "Bett freigeben", bestaetigt: async (el, zu) => { const grund = el.querySelector("[name=grund]").value.trim(); if (grund.length < 3) return meldung("Bitte einen Grund angeben.", "fehler"); try { await aufruf("station", "bettFreigeben", { id: belegung.id, grund }); zu(); meldung("Bett freigegeben.", "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); } /* ---- Bett zuweisen ------------------------------------------------------------ */ function zuweisungsFenster(behaelter, aufnahme) { const d = z.daten; let gewaehltesZimmer = null; // Zimmerobjekt aus dem Bettenspiegel let gewaehltesBett = null; // Bettnummer oder null für „erstes freies" // Nur Stationen mit belegbaren Zimmern anbieten const stationen = d.stationen.filter( (s) => s.aktiv && s.zimmer.some((zi) => zi.verfuegbar)); bestaetigen({ titel: "Bett zuweisen", inhalt: `

${sicher(aufnahme.name)} — ${sicher(aufnahme.grund || "")}

${aufnahme.warnhinweis ? `
⚠
Warnhinweis — ${sicher(aufnahme.warnhinweis)}
` : ""}
${stationen.length ? stationen.map((s) => `
${sicher(s.name)} ${s.kennung !== aufnahme.station ? `andere Station` : ""}
${s.zimmer.filter((zi) => zi.verfuegbar).map((zi) => ` `).join("")}
`).join("") : `
Zurzeit ist kein Zimmer belegbar.
`}
Angezeigt wird nur, was frei und nicht gesperrt ist. Welches Zimmer zu diesem Patienten passt, entscheidest du.
`, bestaetigenText: "Zuweisen", bestaetigt: async (el, zu) => { if (!gewaehltesZimmer) return meldung("Bitte ein Zimmer wählen.", "fehler"); try { const antwort = await aufruf("station", "bettZuweisen", { aufnahme: aufnahme.id, zimmer: gewaehltesZimmer.id, bett: gewaehltesBett, vermerk: el.querySelector("[name=vermerk]").value.trim(), }); zu(); meldung(`Zimmer ${antwort.zimmer}, Bett ${antwort.bett} belegt.`, "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, nachOeffnen: (hg) => { const bettwahl = hg.querySelector("[data-bettwahl]"); const bettreihe = hg.querySelector("[data-bettreihe]"); hg.querySelectorAll("[data-zimmer]").forEach((el) => { el.addEventListener("click", () => { gewaehltesZimmer = findeZimmer(+el.dataset.zimmer); gewaehltesBett = null; hg.querySelectorAll("[data-zimmer]").forEach((k) => k.classList.remove("gewaehlt")); el.classList.add("gewaehlt"); // Freie Betten des gewählten Zimmers zur Auswahl stellen const frei = gewaehltesZimmer.bettenListe.filter((b) => !b.belegung); bettwahl.style.display = frei.length > 1 ? "" : "none"; bettreihe.innerHTML = frei.map((b) => ` `).join(""); bettreihe.querySelectorAll("[data-bett]").forEach((knopf) => { knopf.addEventListener("click", () => { gewaehltesBett = +knopf.dataset.bett; bettreihe.querySelectorAll("[data-bett]") .forEach((k) => k.classList.remove("gewaehlt")); knopf.classList.add("gewaehlt"); }); }); }); }); }, }); } /* ---- Station anlegen und bearbeiten -------------------------------------------- */ function stationsFenster(behaelter, station) { const d = z.daten; const neu = !station; bestaetigen({ titel: neu ? "Station anlegen" : `Station ${station.kennung} bearbeiten`, inhalt: `
${neu ? "" : `
Wird die Kennung geändert, ziehen alle Aufnahmen mit, die auf diese Station verweisen.
`}`, bestaetigenText: neu ? "Anlegen" : "Speichern", bestaetigt: async (el, zu) => { const kennung = el.querySelector("[name=kennung]").value.trim(); const name = el.querySelector("[name=name]").value.trim(); if (!kennung) return meldung("Bitte eine Kennung angeben.", "fehler"); if (!name) return meldung("Bitte einen Namen angeben.", "fehler"); try { await aufruf("station", "stationSpeichern", { id: neu ? null : station.id, kennung, name, art: el.querySelector("[name=art]").value, etage: el.querySelector("[name=etage]").value.trim(), sortierung: +el.querySelector("[name=sortierung]").value, }); zu(); meldung(neu ? "Station angelegt." : "Station gespeichert.", "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); } function aktivFenster(behaelter, station, einschalten) { bestaetigen({ titel: einschalten ? "Station einschalten" : "Station abschalten", gefahr: !einschalten, inhalt: einschalten ? `

Station ${sicher(station.name)} steht danach wieder für Aufnahmen und Belegungen zur Verfügung.

` : `

Station ${sicher(station.name)} wird abgeschaltet. Zimmer und Belegungsverlauf bleiben erhalten — es lässt sich dort nur niemand mehr aufnehmen.

ℹ
Solange dort Patienten liegen, ist das Abschalten nicht möglich.
`, bestaetigenText: einschalten ? "Einschalten" : "Abschalten", bestaetigt: async (el, zu) => { try { await aufruf("station", "stationAktiv", { id: station.id, aktiv: einschalten, grund: el.querySelector("[name=grund]").value.trim(), }); zu(); meldung(einschalten ? "Station eingeschaltet." : "Station abgeschaltet.", "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); } /* ---- Zimmer anlegen und bearbeiten ---------------------------------------------- */ function zimmerBearbeitenFenster(behaelter, zimmer, stationId) { const d = z.daten; const neu = !zimmer; const station = d.stationen.find((s) => s.id === stationId); bestaetigen({ titel: neu ? `Zimmer anlegen · ${station ? station.name : ""}` : `Zimmer ${zimmer.kennung} bearbeiten`, inhalt: `
${neu ? "Ein neues Zimmer startet gesperrt. Erst wenn es jemand freigibt, lässt es sich belegen." : "Belegte Betten lassen sich nicht wegkürzen."}
`, bestaetigenText: neu ? "Anlegen" : "Speichern", bestaetigt: async (el, zu) => { const kennung = el.querySelector("[name=kennung]").value.trim(); if (!kennung) return meldung("Bitte eine Kennung angeben.", "fehler"); try { await aufruf("station", "zimmerSpeichern", { id: neu ? null : zimmer.id, station: stationId, kennung, betten: +el.querySelector("[name=betten]").value, art: el.querySelector("[name=art]").value, hinweis: el.querySelector("[name=hinweis]").value.trim(), }); zu(); meldung(neu ? "Zimmer angelegt." : "Zimmer gespeichert.", "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); } function entfernenFenster(behaelter, zimmer) { bestaetigen({ titel: `Zimmer ${zimmer.kennung} entfernen`, gefahr: true, inhalt: `

Das Zimmer verschwindet aus dem Bettenspiegel.

ℹ
Möglich ist das nur, solange nie jemand darin gelegen hat. Sobald es eine Belegung gab, bleibt das Zimmer bestehen und lässt sich nur sperren.
`, bestaetigenText: "Entfernen", bestaetigt: async (el, zu) => { try { await aufruf("station", "zimmerEntfernen", { id: zimmer.id, grund: el.querySelector("[name=grund]").value.trim(), }); zu(); meldung("Zimmer entfernt.", "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); } /* ---- Laden ------------------------------------------------------------------- */ async function rendern(behaelter) { try { z.daten = await aufruf("station", "uebersicht", { alle: z.zeigeInaktive }); // Sprungziel aus einem Hinweis, z. B. „Zimmer wurde frei" const ziel = App.zustand.modulZiel; if (ziel && ziel.zimmer) { App.zustand.modulZiel = null; uebersicht(behaelter); return zimmerFenster(behaelter, ziel.zimmer); } uebersicht(behaelter); } catch (f) { behaelter.innerHTML = `

Stationen und Belegung

Konnte nicht geladen werden: ${sicher(f.message)}

`; } } App.registriereAnsicht("station", { rendern, aktualisieren() { // Nur auffrischen, wenn kein Fenster offen ist — sonst geht eine Eingabe verloren if (App.zustand.aktivesModul === "station" && !document.querySelector(".fenster-hg")) { rendern(App.$("inhalt")); } }, }); })();