aether-framework/resources/[aether]/aether-medical/html/js/module/station.js
Jerrit Fritzsche e4b82dd685 Aether-Framework: Fachmodule, Admin-Panel und Docker-Datenbank
- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark,
  Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI)
- aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource,
  an aether-core gekoppelt (Rechte über aether_users.admin_group), mit
  WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU)
- Prototypen admin/ vervollständigt
- SQL-Schema aether_admin.sql
- Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 18:55:32 +02:00

850 lines
35 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* =====================================================================
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 `<div class="reiter">
${eintraege.map((e) => `
<button class="${z.reiter === e.id ? "aktiv" : ""}" data-reiter="${e.id}">
${sicher(e.text)}<span class="mdt-zahl">${e.zahl}</span>
</button>`).join("")}
</div>`;
}
function kennzahlen(d) {
const zk = d.zaehler;
const auslastung = zk.betten > 0 ? Math.round((zk.belegt / zk.betten) * 100) : 0;
return `<div class="raster k4">
<div class="kennzahl ${zk.frei === 0 ? "hoch" : zk.frei <= 2 ? "warn" : "gut"}">
<div class="kz-titel">Freie Betten</div>
<div class="kz-wert">${zk.frei}</div>
<div class="kz-zusatz">von ${zk.betten} Betten insgesamt</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Belegt</div>
<div class="kz-wert">${zk.belegt}</div>
<div class="kz-zusatz">${auslastung} % ausgelastet</div>
</div>
<div class="kennzahl ${zk.gesperrt + zk.reinigung > 0 ? "warn" : ""}">
<div class="kz-titel">Nicht belegbar</div>
<div class="kz-wert">${zk.gesperrt + zk.reinigung}</div>
<div class="kz-zusatz">${zk.gesperrt} gesperrt · ${zk.reinigung} in Reinigung</div>
</div>
<div class="kennzahl ${d.ohneBett.length > 0 ? "warn" : ""}">
<div class="kz-titel">Ohne Bett</div>
<div class="kz-wert">${d.ohneBett.length}</div>
<div class="kz-zusatz">aufgenommen, aber in keinem Zimmer</div>
</div>
</div>`;
}
/* ---- Bettenspiegel ------------------------------------------------------ */
/** Ein einzelnes Bett als Zelle */
function bettZelle(bett) {
const b = bett.belegung;
if (!b) {
return `<div class="st-bett frei">
<span class="st-bett-nr">${bett.nummer}</span>
<span class="st-bett-name">frei</span>
</div>`;
}
return `<div class="st-bett belegt"
title="${sicher(b.name)} · seit ${sicher(minuten(b.liegtSeit))}${
b.grund ? " · " + sicher(b.grund) : ""}">
<span class="st-bett-nr">${bett.nummer}</span>
<span class="st-bett-name">${sicher(b.name)}</span>
<span class="st-bett-marker">
${b.warnhinweis ? `<span class="marker kritisch">⚠</span>` : ""}
${b.allergien ? `<span class="marker warn">A</span>` : ""}
${b.aufIntensiv ? `<span class="marker kritisch">ITS</span>` : ""}
${b.prioritaet ? `<span class="marker ${prioArt[b.prioritaet] || "neutral"}">
${b.prioritaet}</span>` : ""}
</span>
</div>`;
}
/** Eine Zimmerkachel */
function zimmerKachel(zi) {
return `<div class="st-zimmer status-${sicher(zi.status)}" data-zimmer="${zi.id}"
title="${sicher(artName(zi.art))}">
<div class="st-zimmer-kopf">
<span class="st-zimmer-name">${sicher(zi.kennung)}</span>
<span class="marker ${statusArt(zi.status, zi.frei)}">
${zi.status === "frei" ? zi.frei + " / " + zi.betten + " frei"
: sicher(statusName(zi.status))}</span>
</div>
<div class="st-betten">${zi.bettenListe.map(bettZelle).join("")}</div>
${zi.hinweis
? `<div class="st-zimmer-fuss">${sicher(zi.hinweis)}</div>` : ""}
</div>`;
}
/** Eine Station mit allen ihren Zimmern */
function stationsKarte(s) {
return `<div class="karte st-station">
<div class="karte-kopf">
<span class="karte-titel">
${sicher(s.name)}
${s.etage ? ` · ${sicher(s.etage)}` : ""}
${s.aktiv ? "" : ` <span class="marker neutral">abgeschaltet</span>`}
</span>
<span class="feld-hinweis">
${s.belegt} von ${s.betten} Betten belegt · ${s.frei} frei
</span>
</div>
${s.zimmer.length
? `<div class="st-zimmerraster">${s.zimmer.map(zimmerKachel).join("")}</div>`
: `<div class="liste-leer">Für diese Station ist kein Zimmer angelegt.</div>`}
</div>`;
}
function spiegelAnsicht(d) {
const stationen = sichtbareStationen();
return stationen.length
? `<div class="st-stationen">${stationen.map(stationsKarte).join("")}</div>`
: `<div class="karte"><div class="liste-leer">Keine Station gefunden.</div></div>`;
}
/* ---- Patienten ohne Bett -------------------------------------------------- */
function ohneBettAnsicht(d) {
return `<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Aufgenommen, aber ohne Bett</span>
<span class="feld-hinweis">Das System legt niemanden von selbst hin</span>
</div>
<div class="liste">
${d.ohneBett.length ? d.ohneBett.map((a) => `
<div class="liste-zeile">
<span class="prio p${sicher(a.prioritaet)}"></span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(a.name)}</div>
<div class="lz-unter">${sicher(String(a.grund || "").slice(0, 90))}</div>
</div>
${a.warnhinweis ? `<span class="marker kritisch" title="${sicher(a.warnhinweis)}">⚠</span>` : ""}
${a.allergien ? `<span class="marker warn" title="Allergien: ${sicher(a.allergien)}">A</span>` : ""}
${a.blutgruppe ? `<span class="marker neutral">${sicher(a.blutgruppe)}</span>` : ""}
${a.stationName ? `<span class="marker info">${sicher(a.stationName)}</span>` : ""}
<span class="db-zeit">seit ${sicher(minuten(a.wartet))}</span>
${d.rechte.belegen
? `<button class="knopf klein" data-zuweisen="${a.id}">Bett zuweisen</button>` : ""}
</div>`).join("")
: `<div class="liste-leer">Jeder aufgenommene Patient liegt in einem Bett.</div>`}
</div>
</div>`;
}
/* ---- Verwaltung ------------------------------------------------------------ */
function verwaltungsAnsicht(d) {
return `
<div class="warnung">
<span class="w-symbol">ℹ</span>
<div>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.</div>
</div>
<div class="filterleiste">
<label class="feld-hinweis">
<input type="checkbox" class="haken" data-inaktive="1"
${z.zeigeInaktive ? "checked" : ""}>
Abgeschaltete Stationen mitzeigen
</label>
<button class="knopf" data-neuestation="1">+ Station anlegen</button>
</div>
${d.stationen.map((s) => `
<div class="karte st-station">
<div class="karte-kopf">
<span class="karte-titel">
<span class="st-kennung">${sicher(s.kennung)}</span>
${sicher(s.name)}
${s.aktiv ? "" : ` <span class="marker neutral">abgeschaltet</span>`}
</span>
<span class="st-verwaltung-knoepfe">
<button class="knopf leise klein" data-station-bearbeiten="${s.id}">Bearbeiten</button>
<button class="knopf leise klein" data-neueszimmer="${s.id}">+ Zimmer</button>
<button class="knopf ${s.aktiv ? "gefahr" : ""} klein"
data-station-aktiv="${s.id}" data-ziel="${s.aktiv ? "0" : "1"}">
${s.aktiv ? "Abschalten" : "Einschalten"}</button>
</span>
</div>
<div class="liste">
${s.zimmer.length ? s.zimmer.map((zi) => `
<div class="liste-zeile">
<span class="db-nummer">${sicher(zi.kennung)}</span>
<div class="lz-haupt">
<div class="lz-titel">${zi.betten} Bett(en) · ${sicher(artName(zi.art))}</div>
<div class="lz-unter">${zi.belegt} belegt${
zi.hinweis ? " · " + sicher(zi.hinweis) : ""}</div>
</div>
<span class="marker ${statusArt(zi.status, zi.frei)}">
${sicher(statusName(zi.status))}</span>
<button class="knopf leise klein" data-zimmer-bearbeiten="${zi.id}">Bearbeiten</button>
${zi.belegt === 0
? `<button class="knopf leise klein" data-zimmer-entfernen="${zi.id}">Entfernen</button>`
: ""}
</div>`).join("")
: `<div class="liste-leer">Noch kein Zimmer angelegt.</div>`}
</div>
</div>`).join("")}
`;
}
/* ---- Gesamtansicht ---------------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>Stationen und Belegung</h2>
<p>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.</p>
</div>
</div>
${kennzahlen(d)}
${reiterleiste(d)}
${z.reiter === "spiegel" ? `
<div class="filterleiste">
<select data-stationsfilter="1">
<option value="">Alle Stationen</option>
${d.stationen.map((s) =>
`<option value="${sicher(s.kennung)}" ${s.kennung === z.station ? "selected" : ""}>
${sicher(s.name)}</option>`).join("")}
</select>
<span class="feld-hinweis">Klick auf ein Zimmer öffnet die Belegung.</span>
</div>` : ""}
${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: `
<div class="werte-liste">
<div><span>Art</span><b>${sicher(artName(zi.art))}</b></div>
<div><span>Status</span><b>${sicher(statusName(zi.status))}</b></div>
<div><span>Betten</span><b>${zi.betten}</b></div>
${zi.etage ? `<div><span>Etage</span><b>${sicher(zi.etage)}</b></div>` : ""}
</div>
${zi.hinweis
? `<div class="warnung warn"><span class="w-symbol">ℹ</span>
<div>${sicher(zi.hinweis)}</div></div>` : ""}
<div class="karte-kopf" style="margin-top:14px">
<span class="karte-titel">Belegung</span>
</div>
<div class="liste">
${d.betten.map((bett) => {
const b = bett.belegung;
if (!b) {
return `<div class="liste-zeile">
<span class="db-nummer">Bett ${bett.nummer}</span>
<div class="lz-haupt"><div class="lz-titel">frei</div></div>
</div>`;
}
return `<div class="liste-zeile">
<span class="db-nummer">Bett ${bett.nummer}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(b.name)}
<span class="pat-geburt">geb. ${sicher(b.geburt)}</span></div>
<div class="lz-unter">liegt seit ${sicher(minuten(b.liegtSeit))} ·
belegt von ${sicher(b.belegtVon)}</div>
</div>
${b.warnhinweis ? `<span class="marker kritisch" title="${sicher(b.warnhinweis)}">⚠</span>` : ""}
${b.allergien ? `<span class="marker warn" title="Allergien: ${sicher(b.allergien)}">A</span>` : ""}
${b.blutgruppe ? `<span class="marker neutral">${sicher(b.blutgruppe)}</span>` : ""}
${b.aufnahme
? `<button class="knopf leise klein" data-akte="${b.aufnahme}">Vorgang</button>` : ""}
${d.rechte.belegen
? `<button class="knopf leise klein" data-freigeben="${b.id}"
data-name="${sicher(b.name)}" data-bett="${bett.nummer}">
Bett freigeben</button>` : ""}
</div>`;
}).join("")}
</div>
${d.rechte.belegen ? `
<div class="feld" style="margin-top:14px">
<label>Zimmerstatus setzen</label>
<div class="feld-reihe">
<select data-neuerstatus="1">
${(d.zimmerstatus || []).map((s) =>
`<option value="${sicher(s.id)}" ${s.id === zi.status ? "selected" : ""}>
${sicher(s.name)}</option>`).join("")}
</select>
<input data-statushinweis="1" maxlength="255"
placeholder="Hinweis zum Zimmer (freiwillig)"
value="${sicher(zi.hinweis || "")}">
<button class="knopf klein" data-statussetzen="1">Setzen</button>
</div>
<div class="feld-hinweis">Ein leeres Zimmer bleibt so lange in Reinigung oder
gesperrt, bis jemand es wieder freigibt. Das System tut das nie.</div>
</div>` : ""}
${d.verlauf.length ? `
<div class="karte-kopf" style="margin-top:14px">
<span class="karte-titel">Frühere Belegungen</span>
<span class="feld-hinweis">bleibt dauerhaft erhalten</span>
</div>
<div class="liste">
${d.verlauf.map((v) => `
<div class="liste-zeile">
<span class="db-nummer">Bett ${v.bett}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(v.name)}</div>
<div class="lz-unter">${zeit(v.belegt_am)} → ${zeit(v.frei_am)} ·
freigegeben von ${sicher(v.freiVon)}</div>
</div>
</div>`).join("")}
</div>` : ""}
`,
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: `
<p><b>${sicher(belegung.name)}</b> liegt in Zimmer
${sicher(belegung.zimmer)}, Bett ${sicher(belegung.bett)}.</p>
<div class="warnung warn">
<span class="w-symbol">🧍</span>
<div><b>Das ist keine Entlassung.</b> Der Patient bleibt aufgenommen und
steht danach ohne Bett auf der Station. Entlassen wird ausschließlich
über die Patientenverwaltung.</div>
</div>
<div class="feld"><label>Grund (Pflicht)</label>
<input name="grund" maxlength="255"
placeholder="Verlegung, Untersuchung, Zimmerwechsel…"></div>`,
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: `
<p><b>${sicher(aufnahme.name)}</b> — ${sicher(aufnahme.grund || "")}</p>
${aufnahme.warnhinweis
? `<div class="warnung kritisch"><span class="w-symbol">⚠</span>
<div><b>Warnhinweis</b> — ${sicher(aufnahme.warnhinweis)}</div></div>` : ""}
<div class="feld"><label>Zimmer wählen</label>
<div class="pat-zimmer">
${stationen.length ? stationen.map((s) => `
<div class="pat-station">
<div class="pat-station-kopf">
<b>${sicher(s.name)}</b>
${s.kennung !== aufnahme.station
? `<span class="marker warn">andere Station</span>` : ""}
</div>
<div class="pat-zimmerreihe">
${s.zimmer.filter((zi) => zi.verfuegbar).map((zi) => `
<button class="pat-zimmer-knopf" data-zimmer="${zi.id}"
title="${sicher(artName(zi.art))}${
zi.hinweis ? " · " + sicher(zi.hinweis) : ""}">
<span class="pz-name">${sicher(zi.kennung)}</span>
<span class="pz-betten">${zi.frei}/${zi.betten} frei</span>
</button>`).join("")}
</div>
</div>`).join("")
: `<div class="liste-leer">Zurzeit ist kein Zimmer belegbar.</div>`}
</div>
<div class="feld-hinweis">Angezeigt wird nur, was frei und nicht gesperrt ist.
Welches Zimmer zu diesem Patienten passt, entscheidest du.</div>
</div>
<div class="feld" data-bettwahl="1" style="display:none">
<label>Bett</label>
<div class="pat-zimmerreihe" data-bettreihe="1"></div>
</div>
<div class="feld"><label>Vermerk (freiwillig)</label>
<input name="vermerk" maxlength="255"></div>`,
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) => `
<button class="pat-zimmer-knopf" data-bett="${b.nummer}">
<span class="pz-name">Bett ${b.nummer}</span>
</button>`).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: `
<div class="feld-reihe">
<div class="feld"><label>Kennung</label>
<input name="kennung" maxlength="24" placeholder="z. B. CH"
value="${neu ? "" : sicher(station.kennung)}"></div>
<div class="feld"><label>Sortierung</label>
<input name="sortierung" type="number" min="0" max="999"
value="${neu ? 0 : station.sortierung}"></div>
</div>
<div class="feld"><label>Name</label>
<input name="name" maxlength="120" placeholder="z. B. Chirurgie"
value="${neu ? "" : sicher(station.name)}"></div>
<div class="feld-reihe">
<div class="feld"><label>Art</label>
<select name="art">
${(d.stationsarten || []).map((a) =>
`<option value="${sicher(a.id)}"
${!neu && a.id === station.art ? "selected" : ""}>
${sicher(a.name)}</option>`).join("")}
</select></div>
<div class="feld"><label>Etage</label>
<input name="etage" maxlength="24" placeholder="z. B. 1. OG"
value="${neu ? "" : sicher(station.etage || "")}"></div>
</div>
${neu ? "" : `
<div class="feld-hinweis">Wird die Kennung geändert, ziehen alle Aufnahmen mit,
die auf diese Station verweisen.</div>`}`,
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
? `<p>Station <b>${sicher(station.name)}</b> steht danach wieder für
Aufnahmen und Belegungen zur Verfügung.</p>
<div class="feld"><label>Vermerk (freiwillig)</label>
<input name="grund" maxlength="255"></div>`
: `<p>Station <b>${sicher(station.name)}</b> wird abgeschaltet. Zimmer und
Belegungsverlauf bleiben erhalten — es lässt sich dort nur niemand
mehr aufnehmen.</p>
<div class="warnung warn"><span class="w-symbol">ℹ</span>
<div>Solange dort Patienten liegen, ist das Abschalten nicht möglich.</div></div>
<div class="feld"><label>Grund (freiwillig)</label>
<input name="grund" maxlength="255"></div>`,
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: `
<div class="feld-reihe">
<div class="feld"><label>Kennung</label>
<input name="kennung" maxlength="24" placeholder="z. B. CH-104"
value="${neu ? "" : sicher(zimmer.kennung)}"></div>
<div class="feld"><label>Betten</label>
<input name="betten" type="number" min="1" max="12"
value="${neu ? 1 : zimmer.betten}"></div>
</div>
<div class="feld"><label>Art</label>
<select name="art">
${(d.zimmerarten || []).map((a) =>
`<option value="${sicher(a.id)}"
${!neu && a.id === zimmer.art ? "selected" : ""}>
${sicher(a.name)}</option>`).join("")}
</select></div>
<div class="feld"><label>Hinweis (freiwillig)</label>
<input name="hinweis" maxlength="255"
placeholder="z. B. Sauerstoffanschluss defekt"
value="${neu ? "" : sicher(zimmer.hinweis || "")}"></div>
<div class="feld-hinweis">${neu
? "Ein neues Zimmer startet gesperrt. Erst wenn es jemand freigibt, lässt es sich belegen."
: "Belegte Betten lassen sich nicht wegkürzen."}</div>`,
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: `
<p>Das Zimmer verschwindet aus dem Bettenspiegel.</p>
<div class="warnung warn">
<span class="w-symbol">ℹ</span>
<div>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.</div>
</div>
<div class="feld"><label>Grund (freiwillig)</label>
<input name="grund" maxlength="255"></div>`,
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 = `<div class="seiten-kopf"><div><h2>Stationen und Belegung</h2>
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
}
}
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"));
}
},
});
})();