/* =====================================================================
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) => `
${sicher(e.text)}${e.zahl}
`).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("")}
`
: ``;
}
/* ---- 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
? `
Bett zuweisen ` : ""}
`).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.
Abgeschaltete Stationen mitzeigen
+ Station anlegen
${d.stationen.map((s) => `
${sicher(s.kennung)}
${sicher(s.name)}
${s.aktiv ? "" : ` abgeschaltet `}
Bearbeiten
+ Zimmer
${s.aktiv ? "Abschalten" : "Einschalten"}
${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))}
Bearbeiten
${zi.belegt === 0
? `
Entfernen `
: ""}
`).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" ? `
Alle Stationen
${d.stationen.map((s) =>
`
${sicher(s.name)} `).join("")}
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
? `` : ""}
Belegung
${d.betten.map((bett) => {
const b = bett.belegung;
if (!b) {
return `
`;
}
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
? `
Vorgang ` : ""}
${d.rechte.belegen
? `
Bett freigeben ` : ""}
`;
}).join("")}
${d.rechte.belegen ? `
` : ""}
${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.
Grund (Pflicht)
`,
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)}
` : ""}
Zimmer wählen
${stationen.length ? stationen.map((s) => `
${sicher(s.name)}
${s.kennung !== aufnahme.station
? `andere Station ` : ""}
${s.zimmer.filter((zi) => zi.verfuegbar).map((zi) => `
${sicher(zi.kennung)}
${zi.frei}/${zi.betten} frei
`).join("")}
`).join("")
: `
Zurzeit ist kein Zimmer belegbar.
`}
Angezeigt wird nur, was frei und nicht gesperrt ist.
Welches Zimmer zu diesem Patienten passt, entscheidest du.
Vermerk (freiwillig)
`,
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) => `
Bett ${b.nummer}
`).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: `
Name
${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.
Vermerk (freiwillig)
`
: `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.
Grund (freiwillig)
`,
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: `
Art
${(d.zimmerarten || []).map((a) =>
`
${sicher(a.name)} `).join("")}
Hinweis (freiwillig)
${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.
Grund (freiwillig)
`,
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"));
}
},
});
})();