- 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>
850 lines
35 KiB
JavaScript
850 lines
35 KiB
JavaScript
/* =====================================================================
|
||
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"));
|
||
}
|
||
},
|
||
});
|
||
})();
|