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