- 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>
748 lines
30 KiB
JavaScript
748 lines
30 KiB
JavaScript
/* =====================================================================
|
||
aether-medical / Ansicht: Intensivstation
|
||
|
||
Diese Ansicht ist ein Monitor für Dokumentation, nicht für Patienten.
|
||
Kein Wert hier wird gemessen: der Zustand steht da, weil ein Arzt ihn
|
||
eingetragen hat, und die Geräteliste ist eine Liste von Namen.
|
||
Ein Aufenthalt endet erst, wenn jemand ihn beendet.
|
||
===================================================================== */
|
||
"use strict";
|
||
|
||
(() => {
|
||
const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen } = App;
|
||
|
||
const z = {
|
||
ordner: "laufend", // laufend | beendet
|
||
offen: null, // geöffneter Aufenthalt
|
||
daten: null,
|
||
};
|
||
|
||
// Marker-Art je Zustand — nur Farbe, keine Wertung
|
||
const zustandArt = { kritisch: "kritisch", stabil: "info", besserung: "gut" };
|
||
|
||
/* ---- Kleine Helfer ---------------------------------------------------- */
|
||
|
||
/** Farbe eines Zustands aus der Konfiguration */
|
||
function zustandFarbe(kennung) {
|
||
const liste = (z.daten && z.daten.zustaende) || [];
|
||
const treffer = liste.find((s) => s.id === kennung);
|
||
return treffer ? treffer.farbe : "#8697a4";
|
||
}
|
||
|
||
/** Zerlegt die Geräteliste in einzelne Namen */
|
||
function geraeteTeile(text) {
|
||
return String(text || "").split(",").map((t) => t.trim()).filter(Boolean);
|
||
}
|
||
|
||
/* ---- Gerüst ------------------------------------------------------------ */
|
||
|
||
function reiterleiste(d) {
|
||
const eintraege = [
|
||
{ id: "laufend", text: "In Betreuung", zahl: d.zaehler.laufend },
|
||
{ id: "beendet", text: "Beendet", zahl: d.zaehler.beendetHeute },
|
||
];
|
||
|
||
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>`;
|
||
}
|
||
|
||
function kennzahlen(d) {
|
||
const zk = d.zaehler;
|
||
|
||
return `<div class="raster k4">
|
||
<div class="kennzahl">
|
||
<div class="kz-titel">In Betreuung</div>
|
||
<div class="kz-wert">${zk.laufend}</div>
|
||
<div class="kz-zusatz">laufende Aufenthalte</div>
|
||
</div>
|
||
<div class="kennzahl ${zk.kritisch > 0 ? "hoch" : ""}">
|
||
<div class="kz-titel">Als kritisch eingetragen</div>
|
||
<div class="kz-wert">${zk.kritisch}</div>
|
||
<div class="kz-zusatz">von Hand eingestuft</div>
|
||
</div>
|
||
<div class="kennzahl ${zk.ohneArzt > 0 ? "hoch" : "gut"}">
|
||
<div class="kz-titel">Ohne zuständigen Arzt</div>
|
||
<div class="kz-wert">${zk.ohneArzt}</div>
|
||
<div class="kz-zusatz">niemand eingetragen</div>
|
||
</div>
|
||
<div class="kennzahl ${zk.langeStill > 0 ? "warn" : ""}">
|
||
<div class="kz-titel">Ohne frischen Eintrag</div>
|
||
<div class="kz-wert">${zk.langeStill}</div>
|
||
<div class="kz-zusatz">seit über ${d.stilleNach} Minuten</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ---- Übersicht ---------------------------------------------------------- */
|
||
|
||
function uebersicht(behaelter) {
|
||
const d = z.daten;
|
||
|
||
behaelter.innerHTML = `
|
||
<div class="seiten-kopf">
|
||
<div>
|
||
<h2>Intensivstation</h2>
|
||
<p>Das System misst nichts und schlägt keinen Alarm. Was hier steht, hat
|
||
jemand eingetragen — der Zustand ändert sich nie von selbst.</p>
|
||
</div>
|
||
${d.rechte.betreuen
|
||
? `<button class="knopf" data-aufnehmen="1">+ Intensivaufnahme</button>` : ""}
|
||
</div>
|
||
|
||
${kennzahlen(d)}
|
||
${reiterleiste(d)}
|
||
|
||
<div class="karte">
|
||
<div class="karte-kopf">
|
||
<span class="karte-titel">${z.ordner === "laufend"
|
||
? "Patienten in Intensivbetreuung" : "Beendete Aufenthalte"}</span>
|
||
<span class="feld-hinweis">${z.ordner === "laufend"
|
||
? "Oben steht, was als am schwersten eingetragen wurde"
|
||
: d.liste.length + " Einträge"}</span>
|
||
</div>
|
||
<div class="liste">
|
||
${d.liste.length ? d.liste.map(zeile).join("")
|
||
: `<div class="liste-leer">${z.ordner === "laufend"
|
||
? "Zurzeit wird niemand intensiv betreut."
|
||
: "Noch keine beendeten Aufenthalte."}</div>`}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
verdrahteUebersicht(behaelter);
|
||
}
|
||
|
||
function zeile(a) {
|
||
const laufend = !a.beendet_am;
|
||
|
||
return `<div class="liste-zeile" data-oeffne="${a.id}">
|
||
<span class="its-punkt" style="background:${sicher(zustandFarbe(a.zustand))}"
|
||
title="${sicher(a.zustandName)}"></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">
|
||
${a.zimmerKennung ? "Zimmer " + sicher(a.zimmerKennung) : "ohne festen Platz"}
|
||
${a.geraete ? " · " + sicher(String(a.geraete).slice(0, 60)) : ""}
|
||
${laufend ? " · zuletzt dokumentiert vor " + sicher(minuten(a.stillSeit)) : ""}
|
||
</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.langeStill ? `<span class="marker warn">Verlauf offen</span>` : ""}
|
||
${laufend && !a.verantwortlichName
|
||
? `<span class="marker kritisch">kein Arzt</span>`
|
||
: a.verantwortlichName
|
||
? `<span class="marker neutral">${sicher(a.verantwortlichName)}</span>` : ""}
|
||
|
||
<span class="marker ${zustandArt[a.zustand] || "neutral"}">
|
||
${sicher(a.zustandName)}</span>
|
||
|
||
<span class="db-zeit">${laufend
|
||
? "seit " + sicher(minuten(a.liegtSeit))
|
||
: zeit(a.beendet_am)}</span>
|
||
</div>`;
|
||
}
|
||
|
||
function verdrahteUebersicht(behaelter) {
|
||
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
|
||
el.addEventListener("click", () => {
|
||
z.ordner = el.dataset.reiter;
|
||
rendern(behaelter);
|
||
});
|
||
});
|
||
|
||
behaelter.querySelectorAll("[data-oeffne]").forEach((el) => {
|
||
el.addEventListener("click", () => oeffne(behaelter, +el.dataset.oeffne));
|
||
});
|
||
|
||
const aufnehmen = behaelter.querySelector("[data-aufnehmen]");
|
||
if (aufnehmen) aufnehmen.addEventListener("click", () => aufnahmeFenster(behaelter));
|
||
}
|
||
|
||
/* ---- Einzelansicht -------------------------------------------------------- */
|
||
|
||
function einzelAnsicht(behaelter, d) {
|
||
const a = d.aufenthalt, s = d.stamm;
|
||
const geraete = geraeteTeile(a.geraete);
|
||
|
||
behaelter.innerHTML = `
|
||
<div class="seiten-kopf">
|
||
<div>
|
||
<h2>${sicher(a.name)}</h2>
|
||
<p>geb. ${sicher(a.geburt)} · ${sicher(a.zustandName)}
|
||
${a.zimmerKennung ? " · Zimmer " + sicher(a.zimmerKennung) : ""}
|
||
${a.stationName ? " · " + sicher(a.stationName) : ""}</p>
|
||
</div>
|
||
<button class="knopf leise" data-zurueck="1">← Übersicht</button>
|
||
</div>
|
||
|
||
${a.laufend ? "" : `
|
||
<div class="warnung">
|
||
<span class="w-symbol">✔</span>
|
||
<div><b>Intensivbetreuung beendet</b> — ${zeit(a.beendetAm)} von
|
||
${sicher(a.beendetVon || "unbekannt")}. Der Aufenthalt ist nur noch
|
||
zum Nachlesen da.</div>
|
||
</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>` : ""}
|
||
|
||
${a.laufend && !a.verantwortlichName ? `
|
||
<div class="warnung kritisch">
|
||
<span class="w-symbol">🧑⚕️</span>
|
||
<div><b>Kein zuständiger Arzt eingetragen.</b> Das System wählt keinen aus —
|
||
jemand muss die Verantwortung übernehmen.</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>Zustand</span>
|
||
<b style="color:${sicher(zustandFarbe(a.zustand))}">
|
||
${sicher(a.zustandName)}</b></div>
|
||
<div class="mdt-wert"><span>In Betreuung seit</span>
|
||
<b>${sicher(minuten(a.liegtSeit))}</b></div>
|
||
<div class="mdt-wert ${a.verantwortlichName ? "" : "fehlt"}"><span>Zuständig</span>
|
||
<b>${sicher(a.verantwortlichName || "niemand eingetragen")}</b></div>
|
||
<div class="mdt-wert ${a.zimmerKennung ? "" : "fehlt"}"><span>Platz</span>
|
||
<b>${sicher(a.zimmerKennung || "kein fester Platz")}</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.dauermedikation ? "" : "fehlt"}"><span>Dauermedikation</span>
|
||
<b>${sicher(s.dauermedikation || "keine erfasst")}</b></div>
|
||
</div>
|
||
|
||
<div class="mdt-kopf-aktionen">
|
||
<button class="knopf leise klein" data-akte="1">Patientenakte öffnen</button>
|
||
${a.aufnahme
|
||
? `<button class="knopf leise klein" data-vorgang="1">Aufnahme öffnen</button>` : ""}
|
||
${a.laufend && d.rechte.betreuen ? `
|
||
<button class="knopf klein" data-zustand="1">Zustand eintragen</button>
|
||
<button class="knopf leise klein" data-geraete="1">Geräte</button>
|
||
<button class="knopf leise klein" data-zustaendig="1">Zuständigkeit</button>
|
||
<button class="knopf gefahr klein" data-beenden="1">Betreuung beenden</button>` : ""}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="db-spalten">
|
||
<div class="karte">
|
||
<div class="karte-kopf"><span class="karte-titel">Aufenthalt</span></div>
|
||
<div class="werte-liste">
|
||
<div><span>Aufgenommen</span><b>${zeit(a.aufgenommenAm)}</b></div>
|
||
<div><span>Aufgenommen von</span><b>${sicher(a.aufgenommenVon)}</b></div>
|
||
${a.aufnahmeGrund
|
||
? `<div><span>Grund der Aufnahme</span><b>${sicher(a.aufnahmeGrund)}</b></div>` : ""}
|
||
${a.beendetAm ? `
|
||
<div><span>Beendet</span><b>${zeit(a.beendetAm)}</b></div>
|
||
<div><span>Beendet von</span><b>${sicher(a.beendetVon)}</b></div>` : ""}
|
||
</div>
|
||
|
||
<div class="karte-kopf" style="margin-top:14px">
|
||
<span class="karte-titel">Angeschlossene Geräte</span>
|
||
<span class="feld-hinweis">nur notiert, nicht gesteuert</span>
|
||
</div>
|
||
${geraete.length
|
||
? `<div class="its-geraete">${geraete.map((g) =>
|
||
`<span class="its-geraet">${sicher(g)}</span>`).join("")}</div>`
|
||
: `<div class="beh-text leer">Nichts eingetragen.</div>`}
|
||
|
||
<div class="karte-kopf" style="margin-top:14px">
|
||
<span class="karte-titel">Grund der Intensivbetreuung</span>
|
||
</div>
|
||
<div class="beh-text ${a.notiz ? "" : "leer"}">
|
||
${sicher(a.notiz || "Nichts eingetragen.")}</div>
|
||
</div>
|
||
|
||
<div class="karte">
|
||
<div class="karte-kopf">
|
||
<span class="karte-titel">Verlauf</span>
|
||
<span class="feld-hinweis">${d.verlauf.length} Einträge</span>
|
||
</div>
|
||
|
||
${a.laufend && d.rechte.betreuen ? `
|
||
<div class="feld">
|
||
<textarea data-neuerverlauf="1" rows="3" maxlength="1000"
|
||
placeholder="Was wurde beobachtet, was wurde getan?"></textarea>
|
||
<div class="feld-reihe" style="margin-top:8px">
|
||
<button class="knopf klein" data-verlaufspeichern="1">Eintrag schreiben</button>
|
||
<span class="feld-hinweis">Der Wortlaut bleibt genau so stehen.</span>
|
||
</div>
|
||
</div>` : ""}
|
||
|
||
${d.verlauf.length ? `
|
||
<div class="mdt-verlauf">
|
||
${d.verlauf.map((v) => `
|
||
<div class="mdt-vl-schritt">
|
||
<div class="mdt-vl-punkt info">✎</div>
|
||
<div class="mdt-vl-inhalt">
|
||
<div class="mdt-vl-kopf">
|
||
<b>${sicher(v.autor)}</b>
|
||
<span class="db-zeit">${zeit(v.am)}</span>
|
||
</div>
|
||
<div class="mdt-vl-text">${sicher(v.text)}</div>
|
||
</div>
|
||
</div>`).join("")}
|
||
</div>`
|
||
: `<div class="liste-leer">Noch nichts dokumentiert.</div>`}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
verdrahteEinzel(behaelter, d);
|
||
}
|
||
|
||
function verdrahteEinzel(behaelter, d) {
|
||
const a = d.aufenthalt;
|
||
|
||
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 vorgang = behaelter.querySelector("[data-vorgang]");
|
||
if (vorgang) {
|
||
vorgang.addEventListener("click", () => {
|
||
if (!App.zustand.module.some((m) => m.id === "patienten")) {
|
||
return meldung("Die Patientenverwaltung steht dir nicht zur Verfügung.", "fehler");
|
||
}
|
||
App.oeffneModul("patienten", { aufnahme: a.aufnahme });
|
||
});
|
||
}
|
||
|
||
const zustand = behaelter.querySelector("[data-zustand]");
|
||
if (zustand) zustand.addEventListener("click", () => zustandFenster(behaelter, d));
|
||
|
||
const geraete = behaelter.querySelector("[data-geraete]");
|
||
if (geraete) geraete.addEventListener("click", () => geraeteFenster(behaelter, d));
|
||
|
||
const zustaendig = behaelter.querySelector("[data-zustaendig]");
|
||
if (zustaendig) zustaendig.addEventListener("click", () => zustaendigFenster(behaelter, d));
|
||
|
||
const beenden = behaelter.querySelector("[data-beenden]");
|
||
if (beenden) beenden.addEventListener("click", () => beendenFenster(behaelter, d));
|
||
|
||
const speichern = behaelter.querySelector("[data-verlaufspeichern]");
|
||
if (speichern) {
|
||
speichern.addEventListener("click", async () => {
|
||
const feld = behaelter.querySelector("[data-neuerverlauf]");
|
||
const text = feld.value.trim();
|
||
if (text.length < 3) return meldung("Bitte einen Text eingeben.", "fehler");
|
||
try {
|
||
await aufruf("intensiv", "verlauf", { id: a.id, text });
|
||
meldung("Eintrag geschrieben.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
});
|
||
}
|
||
}
|
||
|
||
/* ---- Intensivaufnahme ------------------------------------------------------ */
|
||
|
||
async function aufnahmeFenster(behaelter) {
|
||
let k;
|
||
try {
|
||
k = await aufruf("intensiv", "kandidaten", {});
|
||
} catch (f) { return meldung(f.message, "fehler"); }
|
||
|
||
let gewaehlt = null; // Patient/Aufnahme
|
||
let gewaehltesZimmer = null; // Zimmer der Betreuung
|
||
|
||
bestaetigen({
|
||
titel: "Patient auf die Intensivstation aufnehmen",
|
||
inhalt: `
|
||
<div class="feld"><label>Patient (aufgenommen, ohne laufende Intensivbetreuung)</label>
|
||
<div class="liste pat-trefferliste" data-patienten="1">
|
||
${k.patienten.length ? k.patienten.map((p) => `
|
||
<div class="liste-zeile" data-patient="${p.aufnahme}">
|
||
<span class="prio p${sicher(p.prioritaet)}"></span>
|
||
<div class="lz-haupt">
|
||
<div class="lz-titel">${sicher(p.name)}
|
||
<span class="pat-geburt">geb. ${sicher(p.geburt)}</span></div>
|
||
<div class="lz-unter">${sicher(String(p.grund || "").slice(0, 70))}</div>
|
||
</div>
|
||
${p.warnhinweis ? `<span class="marker kritisch">⚠</span>` : ""}
|
||
${p.allergien ? `<span class="marker warn">A</span>` : ""}
|
||
${p.zimmerKennung ? `<span class="marker info">${sicher(p.zimmerKennung)}</span>` : ""}
|
||
</div>`).join("")
|
||
: `<div class="liste-leer">Kein aufgenommener Patient verfügbar.</div>`}
|
||
</div>
|
||
<div class="feld-hinweis">Auf die Intensivstation kommt nur, wer bereits
|
||
aufgenommen ist. Die Reihenfolge ist die eingetragene Dringlichkeit.</div>
|
||
</div>
|
||
|
||
<div class="feld"><label>Zustand bei Übernahme</label>
|
||
<select name="zustand">
|
||
${(z.daten.zustaende || []).map((s) =>
|
||
`<option value="${sicher(s.id)}" ${s.id === "stabil" ? "selected" : ""}>
|
||
${sicher(s.name)}</option>`).join("")}
|
||
</select>
|
||
<div class="feld-hinweis">Deine Einschätzung. Das System leitet sie aus nichts ab.</div>
|
||
</div>
|
||
|
||
<div class="feld"><label>Platz auf der Intensivstation (freiwillig)</label>
|
||
<div class="pat-zimmerreihe" data-zimmerwahl="1">
|
||
${k.zimmer.length ? k.zimmer.map((zi) => `
|
||
<button class="pat-zimmer-knopf ${zi.status === "frei" ? "" : "belegt"}"
|
||
data-zimmer="${zi.id}"
|
||
title="${sicher(zi.stationName)}${
|
||
zi.hinweis ? " · " + sicher(zi.hinweis) : ""}">
|
||
<span class="pz-name">${sicher(zi.kennung)}</span>
|
||
<span class="pz-betten">${zi.belegt}/${zi.betten} belegt</span>
|
||
${zi.status !== "frei"
|
||
? `<span class="pz-status">${sicher(zi.status)}</span>` : ""}
|
||
</button>`).join("")
|
||
: `<div class="liste-leer">Kein Intensivplatz angelegt.</div>`}
|
||
</div>
|
||
<div class="feld-hinweis">Hier wird nur festgehalten, wo betreut wird.
|
||
Das Bett selbst belegt die Stationsverwaltung.</div>
|
||
</div>
|
||
|
||
<div class="feld"><label>Angeschlossene Geräte (freiwillig)</label>
|
||
<input name="geraete" maxlength="512"
|
||
placeholder="z. B. Beatmung, Monitoring, Perfusor"></div>
|
||
|
||
<div class="feld"><label>Grund der Intensivbetreuung (Pflicht)</label>
|
||
<textarea name="notiz" rows="3" maxlength="1000"
|
||
placeholder="Warum wird dieser Patient intensiv betreut?"></textarea></div>
|
||
|
||
<div class="feld"><label>
|
||
<input type="checkbox" class="haken" name="selbst" checked>
|
||
Ich übernehme die Zuständigkeit für diesen Patienten</label>
|
||
<div class="feld-hinweis">Ohne Häkchen bleibt der Aufenthalt ohne zuständigen
|
||
Arzt — das wird allen im Dienst gemeldet.</div></div>`,
|
||
bestaetigenText: "Aufnehmen",
|
||
bestaetigt: async (el, zu) => {
|
||
if (!gewaehlt) return meldung("Bitte zuerst einen Patienten wählen.", "fehler");
|
||
|
||
const notiz = el.querySelector("[name=notiz]").value.trim();
|
||
if (notiz.length < 5) return meldung("Bitte den Grund angeben.", "fehler");
|
||
|
||
try {
|
||
const antwort = await aufruf("intensiv", "aufnehmen", {
|
||
aufnahme: gewaehlt,
|
||
zimmer: gewaehltesZimmer,
|
||
zustand: el.querySelector("[name=zustand]").value,
|
||
geraete: el.querySelector("[name=geraete]").value.trim(),
|
||
notiz,
|
||
selbst: el.querySelector("[name=selbst]").checked,
|
||
});
|
||
zu();
|
||
meldung("Intensivaufenthalt eröffnet.", "gut");
|
||
z.ordner = "laufend";
|
||
oeffne(behaelter, antwort.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
},
|
||
nachOeffnen: (hg) => {
|
||
hg.querySelectorAll("[data-patient]").forEach((el) => {
|
||
el.addEventListener("click", () => {
|
||
gewaehlt = +el.dataset.patient;
|
||
hg.querySelectorAll("[data-patient]")
|
||
.forEach((zeile) => zeile.classList.remove("gewaehlt"));
|
||
el.classList.add("gewaehlt");
|
||
});
|
||
});
|
||
|
||
hg.querySelectorAll("[data-zimmer]").forEach((el) => {
|
||
el.addEventListener("click", () => {
|
||
// Ein zweiter Klick nimmt die Wahl wieder zurück
|
||
const schonGewaehlt = gewaehltesZimmer === +el.dataset.zimmer;
|
||
hg.querySelectorAll("[data-zimmer]").forEach((k) => k.classList.remove("gewaehlt"));
|
||
gewaehltesZimmer = schonGewaehlt ? null : +el.dataset.zimmer;
|
||
if (!schonGewaehlt) el.classList.add("gewaehlt");
|
||
});
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Zustand eintragen ------------------------------------------------------- */
|
||
|
||
function zustandFenster(behaelter, d) {
|
||
const a = d.aufenthalt;
|
||
|
||
bestaetigen({
|
||
titel: "Zustand eintragen",
|
||
inhalt: `
|
||
<p>Aktuell eingetragen: <b>${sicher(a.zustandName)}</b>
|
||
— von ${sicher(a.verantwortlichName || "niemandem")} verantwortet.</p>
|
||
|
||
<div class="feld"><label>Neue Einschätzung</label>
|
||
<select name="zustand">
|
||
${(d.zustaende || []).map((s) =>
|
||
`<option value="${sicher(s.id)}" ${s.id === a.zustand ? "selected" : ""}>
|
||
${sicher(s.name)}</option>`).join("")}
|
||
</select></div>
|
||
|
||
<div class="feld"><label>Begründung (Pflicht)</label>
|
||
<textarea name="grund" rows="3" maxlength="1000"
|
||
placeholder="Worauf stützt sich die Einschätzung?"></textarea>
|
||
<div class="feld-hinweis">Dieser Text wandert unverändert in den Verlauf.</div></div>`,
|
||
bestaetigenText: "Eintragen",
|
||
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("intensiv", "zustand", {
|
||
id: a.id,
|
||
zustand: el.querySelector("[name=zustand]").value,
|
||
grund,
|
||
});
|
||
zu();
|
||
meldung("Zustand eingetragen.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Geräte ------------------------------------------------------------------- */
|
||
|
||
function geraeteFenster(behaelter, d) {
|
||
const a = d.aufenthalt;
|
||
const vorschlaege = d.geraeteliste || [];
|
||
const vorhanden = geraeteTeile(a.geraete);
|
||
|
||
// Alles, was nicht in der Vorschlagsliste steht, bleibt als Freitext erhalten
|
||
const eigene = vorhanden.filter((g) => !vorschlaege.includes(g));
|
||
|
||
bestaetigen({
|
||
titel: "Angeschlossene Geräte",
|
||
inhalt: `
|
||
<div class="warnung">
|
||
<span class="w-symbol">ℹ</span>
|
||
<div>Das System steuert kein Gerät, liest keinen Wert aus und schlägt keinen
|
||
Alarm. Es hält fest, was jemand angeschlossen hat.</div>
|
||
</div>
|
||
|
||
<div class="feld"><label>Aus der Liste</label>
|
||
<div class="its-geraetewahl">
|
||
${vorschlaege.map((g) => `
|
||
<button class="its-geraet-knopf ${vorhanden.includes(g) ? "gewaehlt" : ""}"
|
||
data-geraet="${sicher(g)}">${sicher(g)}</button>`).join("")}
|
||
</div></div>
|
||
|
||
<div class="feld"><label>Weitere (mit Komma trennen)</label>
|
||
<input name="eigene" maxlength="512" value="${sicher(eigene.join(", "))}"></div>`,
|
||
bestaetigenText: "Speichern",
|
||
bestaetigt: async (el, zu) => {
|
||
const gewaehlt = Array.from(el.querySelectorAll(".its-geraet-knopf.gewaehlt"))
|
||
.map((k) => k.dataset.geraet);
|
||
const eigeneTexte = geraeteTeile(el.querySelector("[name=eigene]").value);
|
||
|
||
try {
|
||
await aufruf("intensiv", "geraete", {
|
||
id: a.id,
|
||
geraete: gewaehlt.concat(eigeneTexte).join(", "),
|
||
});
|
||
zu();
|
||
meldung("Geräte eingetragen.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
},
|
||
nachOeffnen: (hg) => {
|
||
hg.querySelectorAll("[data-geraet]").forEach((el) => {
|
||
el.addEventListener("click", () => el.classList.toggle("gewaehlt"));
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Zuständigkeit -------------------------------------------------------------- */
|
||
|
||
async function zustaendigFenster(behaelter, d) {
|
||
const a = d.aufenthalt;
|
||
|
||
let b;
|
||
try {
|
||
b = await aufruf("intensiv", "betreuer", {});
|
||
} catch (f) { return meldung(f.message, "fehler"); }
|
||
|
||
let gewaehlt = a.verantwortlich || null;
|
||
let niederlegen = false;
|
||
|
||
bestaetigen({
|
||
titel: "Zuständigkeit",
|
||
inhalt: `
|
||
<p>Zuständig ist zurzeit:
|
||
<b>${sicher(a.verantwortlichName || "niemand")}</b></p>
|
||
|
||
<div class="feld"><label>Im Dienst und berechtigt</label>
|
||
<div class="liste pat-trefferliste" data-betreuer="1">
|
||
${b.liste.length ? b.liste.map((p) => `
|
||
<div class="liste-zeile ${p.charakterId === a.verantwortlich ? "gewaehlt" : ""}"
|
||
data-person="${p.charakterId}">
|
||
<div class="lz-haupt">
|
||
<div class="lz-titel">${sicher(p.name)}
|
||
${p.selbst ? `<span class="marker info">du</span>` : ""}</div>
|
||
<div class="lz-unter">${sicher(p.rolleName)} ·
|
||
${sicher(p.personalnummer || "")}</div>
|
||
</div>
|
||
<span class="marker neutral">${sicher(p.funktion || "")}</span>
|
||
</div>`).join("")
|
||
: `<div class="liste-leer">Niemand mit diesem Recht ist im Dienst.</div>`}
|
||
</div>
|
||
<div class="feld-hinweis">Das System verteilt keine Zuständigkeit —
|
||
sie wird übernommen oder übertragen.</div></div>
|
||
|
||
<div class="feld"><label>
|
||
<input type="checkbox" class="haken" name="niederlegen">
|
||
Zuständigkeit niederlegen (niemand ist danach eingetragen)</label></div>
|
||
|
||
<div class="feld"><label>Vermerk${a.verantwortlich ? " / Grund" : " (freiwillig)"}</label>
|
||
<input name="grund" maxlength="255"
|
||
placeholder="Übergabe, Dienstende, Fachwechsel…"></div>`,
|
||
bestaetigenText: "Übernehmen",
|
||
bestaetigt: async (el, zu) => {
|
||
const grund = el.querySelector("[name=grund]").value.trim();
|
||
|
||
if (niederlegen && !grund) {
|
||
return meldung("Bitte angeben, warum niemand mehr zuständig ist.", "fehler");
|
||
}
|
||
if (!niederlegen && !gewaehlt) {
|
||
return meldung("Bitte eine Person wählen.", "fehler");
|
||
}
|
||
|
||
try {
|
||
await aufruf("intensiv", "verantwortlich", {
|
||
id: a.id,
|
||
charakter: niederlegen ? null : gewaehlt,
|
||
grund,
|
||
});
|
||
zu();
|
||
meldung(niederlegen ? "Zuständigkeit niedergelegt." : "Zuständigkeit eingetragen.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
},
|
||
nachOeffnen: (hg) => {
|
||
hg.querySelectorAll("[data-person]").forEach((el) => {
|
||
el.addEventListener("click", () => {
|
||
gewaehlt = +el.dataset.person;
|
||
hg.querySelectorAll("[data-person]")
|
||
.forEach((zeile) => zeile.classList.remove("gewaehlt"));
|
||
el.classList.add("gewaehlt");
|
||
});
|
||
});
|
||
|
||
const haken = hg.querySelector("[name=niederlegen]");
|
||
haken.addEventListener("change", () => {
|
||
niederlegen = haken.checked;
|
||
// Die Personenliste wird unbrauchbar, sobald niedergelegt wird
|
||
hg.querySelector("[data-betreuer]").style.opacity = niederlegen ? ".45" : "";
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Betreuung beenden ------------------------------------------------------------ */
|
||
|
||
function beendenFenster(behaelter, d) {
|
||
const a = d.aufenthalt;
|
||
|
||
bestaetigen({
|
||
titel: "Intensivbetreuung beenden",
|
||
gefahr: true,
|
||
inhalt: `
|
||
<p><b>${sicher(a.name)}</b> wird ab sofort nicht mehr intensiv betreut.</p>
|
||
|
||
<div class="warnung warn">
|
||
<span class="w-symbol">ℹ</span>
|
||
<div><b>Das ist keine Entlassung.</b> Der Patient bleibt aufgenommen, sein Bett
|
||
bleibt belegt und keine Behandlung wird dadurch abgeschlossen.</div>
|
||
</div>
|
||
|
||
${a.zustand === "kritisch" ? `
|
||
<div class="warnung kritisch">
|
||
<span class="w-symbol">⚠</span>
|
||
<div>Der Zustand steht weiterhin auf <b>kritisch</b>. Das Beenden ist
|
||
trotzdem möglich — es wird so festgehalten.</div>
|
||
</div>` : ""}
|
||
|
||
<div class="feld"><label>Begründung (Pflicht)</label>
|
||
<textarea name="grund" rows="4" maxlength="1000"
|
||
placeholder="Zustand bei Verlegung auf Normalstation, weiteres Vorgehen…"></textarea>
|
||
<div class="feld-hinweis">Dieser Text schließt den Verlauf ab.</div></div>`,
|
||
bestaetigenText: "Betreuung beenden",
|
||
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("intensiv", "beenden", { id: a.id, grund });
|
||
zu();
|
||
meldung("Intensivbetreuung beendet.", "gut");
|
||
z.offen = null;
|
||
z.ordner = "beendet";
|
||
rendern(behaelter);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Laden ----------------------------------------------------------------------- */
|
||
|
||
async function oeffne(behaelter, intensivId) {
|
||
try {
|
||
const d = await aufruf("intensiv", "oeffnen", { id: intensivId });
|
||
z.offen = intensivId;
|
||
einzelAnsicht(behaelter, d);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
}
|
||
|
||
async function rendern(behaelter) {
|
||
try {
|
||
z.daten = await aufruf("intensiv", "uebersicht", { ordner: z.ordner });
|
||
|
||
// Sprungziel aus einem Hinweis, z. B. „als kritisch eingetragen"
|
||
const ziel = App.zustand.modulZiel;
|
||
if (ziel && ziel.intensiv) {
|
||
App.zustand.modulZiel = null;
|
||
return oeffne(behaelter, ziel.intensiv);
|
||
}
|
||
|
||
if (z.offen) await oeffne(behaelter, z.offen);
|
||
else uebersicht(behaelter);
|
||
} catch (f) {
|
||
behaelter.innerHTML = `<div class="seiten-kopf"><div><h2>Intensivstation</h2>
|
||
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
|
||
}
|
||
}
|
||
|
||
App.registriereAnsicht("intensiv", {
|
||
rendern,
|
||
aktualisieren() {
|
||
// Eine offene Einzelansicht oder ein offenes Fenster nicht stören
|
||
if (App.zustand.aktivesModul === "intensiv" && !z.offen
|
||
&& !document.querySelector(".fenster-hg")) {
|
||
rendern(App.$("inhalt"));
|
||
}
|
||
},
|
||
});
|
||
})();
|