aether-framework/resources/[aether]/aether-medical/html/js/module/operation.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

736 lines
30 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: Operationsverwaltung
OP-Plan, OP-Säle und Operationsbericht. Der geplante Zeitpunkt ist
eine Erinnerung — beginnen und beenden trägt das Team von Hand ein.
Überschneidungen im Saal werden angezeigt, aber nicht verhindert.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen } = App;
const z = {
ordner: "plan", // plan | laufend | archiv
daten: null,
offen: null,
};
const statusArt = {
geplant: "info", laufend: "warn", abgeschlossen: "gut", abgesagt: "neutral",
};
/* ---- Übersicht ----------------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>Operationen</h2>
<p>Der OP-Plan erinnert an Termine. Begonnen und beendet wird eine
Operation ausschließlich durch Eintrag des OP-Teams.</p>
</div>
${d.rechte.planen
? `<button class="knopf" data-neu="1">+ Operation planen</button>` : ""}
</div>
<div class="raster k4">
<div class="kennzahl">
<div class="kz-titel">Geplant</div>
<div class="kz-wert">${d.zaehler.geplant}</div>
<div class="kz-zusatz">im OP-Plan</div>
</div>
<div class="kennzahl ${d.zaehler.faellig > 0 ? "hoch" : ""}">
<div class="kz-titel">Termin erreicht</div>
<div class="kz-wert">${d.zaehler.faellig}</div>
<div class="kz-zusatz">wartet auf Beginn</div>
</div>
<div class="kennzahl ${d.zaehler.laufend > 0 ? "warn" : ""}">
<div class="kz-titel">Laufend</div>
<div class="kz-wert">${d.zaehler.laufend}</div>
<div class="kz-zusatz">Eingriffe im Gange</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Heute beendet</div>
<div class="kz-wert">${d.zaehler.heute}</div>
<div class="kz-zusatz">abgeschlossen</div>
</div>
</div>
<div class="karte" style="margin-bottom:16px">
<div class="karte-kopf">
<span class="karte-titel">OP-Säle</span>
<span class="feld-hinweis">Belegung ergibt sich aus laufenden Eingriffen</span>
</div>
<div class="op-saele">
${d.saele.length ? d.saele.map(saalKachel).join("")
: `<div class="liste-leer">Keine OP-Säle angelegt.</div>`}
</div>
</div>
<div class="reiter">
<button class="${z.ordner === "plan" ? "aktiv" : ""}" data-reiter="plan">
OP-Plan<span class="mdt-zahl">${d.zaehler.geplant + d.zaehler.laufend}</span></button>
<button class="${z.ordner === "laufend" ? "aktiv" : ""}" data-reiter="laufend">
Laufend<span class="mdt-zahl">${d.zaehler.laufend}</span></button>
<button class="${z.ordner === "archiv" ? "aktiv" : ""}" data-reiter="archiv">
Archiv</button>
</div>
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">${z.ordner === "archiv"
? "Abgeschlossene und abgesagte Operationen" : "Eingriffe"}</span>
<span class="feld-hinweis">${d.liste.length} Einträge</span>
</div>
<div class="liste">
${d.liste.length ? d.liste.map(zeile).join("")
: `<div class="liste-leer">Nichts geplant.</div>`}
</div>
</div>
`;
verdrahteUebersicht(behaelter);
}
function saalKachel(s) {
const belegt = !!s.laufend;
const gesperrt = s.status === "gesperrt";
return `<div class="op-saal ${belegt ? "belegt" : gesperrt ? "gesperrt" : "frei"}">
<div class="op-saal-kopf">
<span class="op-saal-name">${sicher(s.kennung)}</span>
<span class="marker ${belegt ? "warn" : gesperrt ? "kritisch" : "gut"}">
${belegt ? "belegt" : gesperrt ? "gesperrt" : "frei"}</span>
</div>
${belegt ? `
<div class="op-saal-inhalt">
<b>${sicher(s.laufend.kennung)}</b> · ${sicher(s.laufend.art)}
<div class="feld-hinweis">${sicher(s.laufend.patientName)}
· läuft seit ${minuten(s.laufend.dauer)}</div>
</div>`
: `<div class="op-saal-inhalt">
<div class="feld-hinweis">${s.geplant} geplante Eingriffe
${s.hinweis ? " · " + sicher(s.hinweis) : ""}</div>
</div>`}
</div>`;
}
function zeile(o) {
const laufend = o.status === "laufend";
const faellig = o.status === "geplant" && o.restMinuten !== null && o.restMinuten <= 0;
return `<div class="liste-zeile" data-oeffne="${o.id}">
<span class="db-nummer">${sicher(o.kennung)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(o.art)}
<span class="pat-geburt">${sicher(o.patientName)}</span></div>
<div class="lz-unter">
${o.saalName ? "Saal " + sicher(o.saalName) : "kein Saal"}
· ${o.teamGroesse} im Team
· geplant von ${sicher(o.erstelltVon)}
</div>
</div>
${o.hinweis ? `<span class="marker kritisch" title="${sicher(o.hinweis)}">⚠</span>` : ""}
${o.allergien ? `<span class="marker warn" title="Allergien: ${sicher(o.allergien)}">A</span>` : ""}
${o.blutgruppe ? `<span class="marker neutral">${sicher(o.blutgruppe)}</span>` : ""}
${o.hatKomplikationen ? `<span class="marker kritisch">Komplikationen</span>` : ""}
${o.ichDabei ? `<span class="marker gut">du bist im Team</span>` : ""}
${faellig ? `<span class="marker warn">Termin erreicht</span>` : ""}
<span class="marker ${statusArt[o.status] || "neutral"}">${sicher(o.status)}</span>
<span class="db-zeit">${laufend
? "läuft seit " + minuten(o.dauerMinuten)
: o.status === "geplant"
? (o.geplant_fuer
? (o.restMinuten > 0 ? "in " + minuten(o.restMinuten) : "seit " + minuten(-o.restMinuten) + " fällig")
: "ohne Termin")
: zeit(o.ende)}</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 neu = behaelter.querySelector("[data-neu]");
if (neu) neu.addEventListener("click", () => planungsFenster(behaelter));
}
/* ---- Einzelansicht -------------------------------------------------------- */
function einzelAnsicht(behaelter, d) {
const o = d.operation, s = d.stamm;
const geplant = o.status === "geplant";
const laufend = o.status === "laufend";
const beendet = o.status === "abgeschlossen" || o.status === "abgesagt";
const schreibbar = !beendet && d.rechte.dokumentieren;
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>${sicher(o.kennung)} · ${sicher(o.art)}</h2>
<p>${sicher(o.patientName)} · geb. ${sicher(o.geburt)}
${o.saalName ? " · Saal " + sicher(o.saalName) : ""}</p>
</div>
<button class="knopf leise" data-zurueck="1">← OP-Plan</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>` : ""}
${s.implantate ? `
<div class="warnung warn">
<span class="w-symbol">🔩</span>
<div><b>Implantate</b> — ${sicher(s.implantate)}</div>
</div>` : ""}
${d.ueberschneidungen.length ? `
<div class="warnung warn">
<span class="w-symbol">🕐</span>
<div><b>Zeitgleich im selben Saal:</b>
${d.ueberschneidungen.map((u) =>
`${sicher(u.kennung)} (${sicher(u.patientName)})`).join(", ")}
<br><span class="feld-hinweis">Das System verhindert nichts — vielleicht
ist der andere Eingriff früher fertig. Die Entscheidung liegt bei dir.</span>
</div>
</div>` : ""}
<div class="beh-kopf">
<div class="werte-liste">
<div><span>Status</span><b>${sicher(o.status)}</b></div>
<div><span>Geplant für</span><b>${o.geplantFuer
? zeit(o.geplantFuer) : "ohne Termin"}</b></div>
<div><span>Beginn</span><b>${o.beginn ? zeit(o.beginn) : "noch nicht begonnen"}</b></div>
<div><span>Ende</span><b>${o.ende ? zeit(o.ende) : "—"}</b></div>
<div><span>Geplant von</span><b>${sicher(o.erstelltVon)}</b></div>
<div class="${s.blutgruppe ? "" : "leer"}"><span>Blutgruppe</span>
<b>${sicher(s.blutgruppe || "nicht bestimmt")}</b></div>
</div>
<div class="beh-kopf-aktionen">
<button class="knopf leise klein" data-akte="1">Patientenakte</button>
${geplant && d.rechte.dokumentieren
? `<button class="knopf klein" data-beginnen="1">Operation beginnen</button>` : ""}
${laufend && d.rechte.dokumentieren
? `<button class="knopf klein" data-beenden="1">Operation beenden</button>` : ""}
${geplant && d.rechte.planen
? `<button class="knopf leise klein" data-umplanen="1">Umplanen</button>` : ""}
${!beendet && d.rechte.dokumentieren
? `<button class="knopf leise klein" data-team="1">
${d.ichDabei ? "Team verlassen" : "Ins Team eintreten"}</button>` : ""}
${!beendet && d.rechte.planen
? `<button class="knopf gefahr klein" data-absagen="1">Absagen</button>` : ""}
</div>
</div>
<div class="db-spalten" style="margin-bottom:14px">
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">OP-Team</span>
${!beendet && d.rechte.planen
? `<button class="knopf klein" data-teamzu="1">+ Mitglied</button>` : ""}
</div>
<div class="liste">
${d.team.length ? d.team.map((t) => `
<div class="liste-zeile">
<span class="marker ${t.funktion === "operateur" ? "gut" : "neutral"}">
${sicher(t.funktionName)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(t.name)}</div>
<div class="lz-unter">${sicher(t.personalnummer || "—")}
· ${sicher(t.rolleName || "")}</div>
</div>
${!beendet && d.rechte.planen
? `<button class="knopf leise klein" data-teamweg="${t.character}"
title="Aus dem Team nehmen">✕</button>` : ""}
</div>`).join("")
: `<div class="liste-leer">Noch niemand eingeteilt.</div>`}
</div>
</div>
<div class="karte">
<div class="karte-kopf"><span class="karte-titel">Relevante Vorgeschichte</span></div>
<div class="werte-liste">
<div class="${s.vorerkrankungen ? "" : "leer"}"><span>Vorerkrankungen</span>
<b>${sicher(s.vorerkrankungen || "keine erfasst")}</b></div>
<div class="${s.dauermedikation ? "" : "leer"}"><span>Dauermedikation</span>
<b>${sicher(s.dauermedikation || "keine erfasst")}</b></div>
<div class="${s.implantate ? "" : "leer"}"><span>Implantate</span>
<b>${sicher(s.implantate || "keine erfasst")}</b></div>
${o.aufnahmeGrund
? `<div><span>Aufnahmegrund</span><b>${sicher(o.aufnahmeGrund)}</b></div>` : ""}
</div>
<div class="karte-fuss">
Fehlende Angaben heißen „nicht erhoben". Das System schließt daraus nichts.
</div>
</div>
</div>
<div class="beh-bogen">
${bogenFeld("verlauf", "Operationsverlauf", o.verlauf, schreibbar,
"Zugang, Vorgehen, Befund, Versorgung…")}
${bogenFeld("komplikationen", "Komplikationen", o.komplikationen, schreibbar,
"Nur eintragen, wenn welche auftraten.")}
${bogenFeld("nachsorge", "Nachsorge", o.nachsorge, schreibbar,
"Drainagen, Verbandwechsel, Kontrolle, Belastung…")}
</div>
${schreibbar ? `
<div style="margin-top:14px">
<button class="knopf" data-speichern="1">OP-Bericht speichern</button>
</div>` : ""}
`;
verdrahteEinzel(behaelter, d);
}
/** Ein Feld des Operationsberichts */
function bogenFeld(name, titel, wert, schreibbar, hinweis) {
if (!schreibbar) {
return `<div class="beh-feld gesperrt">
<label>${sicher(titel)}</label>
<div class="beh-text ${wert ? "" : "leer"}">${wert
? sicher(wert).replace(/\n/g, "<br>") : "Nicht dokumentiert."}</div>
</div>`;
}
return `<div class="beh-feld">
<label>${sicher(titel)}</label>
<textarea name="${sicher(name)}" rows="6"
placeholder="${sicher(hinweis || "")}">${sicher(wert || "")}</textarea>
<div class="feld-hinweis">${sicher(hinweis || "")}</div>
</div>`;
}
function verdrahteEinzel(behaelter, d) {
const o = d.operation;
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: o.patient });
});
/** Liest die Felder des Berichts aus */
const bericht = () => ({
verlauf: (behaelter.querySelector("[name=verlauf]") || {}).value,
komplikationen: (behaelter.querySelector("[name=komplikationen]") || {}).value,
nachsorge: (behaelter.querySelector("[name=nachsorge]") || {}).value,
});
const speichern = behaelter.querySelector("[data-speichern]");
if (speichern) speichern.addEventListener("click", async () => {
try {
await aufruf("operation", "dokumentieren",
Object.assign({ id: o.id }, bericht()));
meldung("OP-Bericht gespeichert.", "gut");
oeffne(behaelter, o.id);
} catch (f) { meldung(f.message, "fehler"); }
});
const beginnen = behaelter.querySelector("[data-beginnen]");
if (beginnen) beginnen.addEventListener("click", () => {
bestaetigen({
titel: "Operation beginnen",
inhalt: `<p><b>${sicher(o.kennung)}</b> — ${sicher(o.art)}
bei ${sicher(o.patientName)}.</p>
<p>Der Beginn wird mit der aktuellen Uhrzeit vermerkt und der Saal gilt
als belegt. ${o.geplantFuer
? "Der geplante Termin war " + zeit(o.geplantFuer) + "." : ""}</p>
${d.team.length === 0 ? `
<div class="warnung warn">
<span class="w-symbol">👥</span>
<div><b>Es ist noch niemand im OP-Team.</b> Du wirst mit dem Beginn
als Beteiligter eingetragen.</div>
</div>` : ""}`,
bestaetigenText: "Beginn eintragen",
bestaetigt: async (_, zu) => {
try {
await aufruf("operation", "beginnen", { id: o.id });
zu();
meldung("Beginn eingetragen.", "gut");
oeffne(behaelter, o.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const beenden = behaelter.querySelector("[data-beenden]");
if (beenden) beenden.addEventListener("click", () => {
const aktuell = bericht();
bestaetigen({
titel: "Operation beenden",
inhalt: `<p><b>${sicher(o.kennung)}</b> wird abgeschlossen. Der Bericht
ist danach schreibgeschützt.</p>
${!aktuell.verlauf && !o.verlauf ? `
<div class="warnung warn">
<span class="w-symbol">📄</span>
<div><b>Der Operationsverlauf ist leer.</b> Ohne Verlauf lässt sich
nicht abschließen — bitte zuerst dokumentieren.</div>
</div>` : ""}
<p class="feld-hinweis">Was gerade im Bogen steht, wird mit übernommen.</p>`,
bestaetigenText: "Beenden",
gefahr: true,
bestaetigt: async (_, zu) => {
try {
await aufruf("operation", "beenden",
Object.assign({ id: o.id }, aktuell));
zu();
meldung("Operation abgeschlossen.", "gut");
oeffne(behaelter, o.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const umplanen = behaelter.querySelector("[data-umplanen]");
if (umplanen) umplanen.addEventListener("click", () => umplanungsFenster(behaelter, d));
const absagen = behaelter.querySelector("[data-absagen]");
if (absagen) absagen.addEventListener("click", () => {
bestaetigen({
titel: "Operation absagen",
gefahr: true,
inhalt: `<p><b>${sicher(o.kennung)}</b> — ${sicher(o.art)}</p>
<p>Die Operation wird als abgesagt abgelegt. Die Begründung wandert
in den Verlauf und bleibt dauerhaft lesbar.</p>
<div class="feld"><label>Begründung (Pflicht)</label>
<textarea name="grund" rows="3"
placeholder="z. B. Patient nicht nüchtern, Zustand verschlechtert"></textarea></div>`,
bestaetigenText: "Absagen",
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("operation", "absagen", { id: o.id, grund });
zu();
meldung("Operation abgesagt.", "gut");
z.offen = null;
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const team = behaelter.querySelector("[data-team]");
if (team) team.addEventListener("click", async () => {
try {
const antwort = await aufruf("operation", "teamWechsel", {
id: o.id, beitreten: !d.ichDabei, funktion: "assistenz",
});
meldung(antwort.dabei ? "Du bist im OP-Team." : "Team verlassen.", "gut");
oeffne(behaelter, o.id);
} catch (f) { meldung(f.message, "fehler"); }
});
const teamZu = behaelter.querySelector("[data-teamzu]");
if (teamZu) teamZu.addEventListener("click", () => teamFenster(behaelter, d));
behaelter.querySelectorAll("[data-teamweg]").forEach((el) => {
el.addEventListener("click", async () => {
try {
await aufruf("operation", "teamEntfernen", {
id: o.id, charakterId: +el.dataset.teamweg,
});
meldung("Aus dem Team genommen.", "gut");
oeffne(behaelter, o.id);
} catch (f) { meldung(f.message, "fehler"); }
});
});
}
/* ---- Team ergänzen --------------------------------------------------------- */
async function teamFenster(behaelter, d) {
const o = d.operation;
let auswahl;
try {
auswahl = await aufruf("operation", "kollegen", {});
} catch (f) { return meldung(f.message, "fehler"); }
const imTeam = new Set(d.team.map((t) => t.character));
const frei = (auswahl.kollegen || []).filter((k) => !imTeam.has(k.charakterId));
let gewaehlt = null;
fenster({
titel: "OP-Team ergänzen",
inhalt: `
<div class="feld"><label>Kollege im Dienst</label>
<div class="liste pat-trefferliste">
${frei.length ? frei.map((k) => `
<div class="liste-zeile" data-kollege="${k.charakterId}">
<span class="db-nummer">${sicher(k.personalnummer)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(k.name)}</div>
<div class="lz-unter">${sicher(k.rolleName)}</div>
</div>
</div>`).join("")
: `<div class="liste-leer">Niemand weiter im Dienst.</div>`}
</div></div>
<div class="feld"><label>Funktion im Team</label>
<select name="funktion">
${d.funktionen.map((f) =>
`<option value="${sicher(f.id)}">${sicher(f.name)}</option>`).join("")}
</select>
<div class="feld-hinweis">Die Einteilung ist eine Zuordnung im Bericht —
teilnehmen muss der Kollege trotzdem selbst.</div></div>`,
knoepfe: [
{ text: "Abbrechen", art: "leise", klick: (_, zu) => zu() },
{ text: "Einteilen", klick: async (el, zu) => {
if (!gewaehlt) return meldung("Bitte einen Kollegen wählen.", "fehler");
try {
await aufruf("operation", "teamSetzen", {
id: o.id, charakterId: gewaehlt,
funktion: el.querySelector("[name=funktion]").value,
});
zu();
meldung("Ins Team eingeteilt.", "gut");
oeffne(behaelter, o.id);
} catch (f) { meldung(f.message, "fehler"); }
} },
],
nachOeffnen: (hg) => {
hg.querySelectorAll("[data-kollege]").forEach((el) => {
el.addEventListener("click", () => {
gewaehlt = +el.dataset.kollege;
hg.querySelectorAll("[data-kollege]")
.forEach((k) => k.classList.remove("gewaehlt"));
el.classList.add("gewaehlt");
});
});
},
});
}
/* ---- Planen und Umplanen ---------------------------------------------------- */
function planungsFenster(behaelter) {
let gewaehlt = null;
let saele = [];
bestaetigen({
titel: "Operation planen",
inhalt: `
<div class="feld"><label>Patient 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>Art des Eingriffs</label>
<input name="art" maxlength="160"
placeholder="z. B. Laparotomie, Wundversorgung Oberschenkel"></div>
<div class="feld-reihe">
<div class="feld"><label>OP-Saal</label>
<select name="saal"><option value="">— später festlegen —</option></select></div>
<div class="feld"><label>Beginn in … Minuten</label>
<input name="vorlauf" type="number" min="0" max="20160" value="30">
<div class="feld-hinweis">0 = ohne Termin</div></div>
</div>
<div class="feld">
<label><input type="checkbox" name="operateur" class="haken" checked>
Ich bin der Operateur</label>
</div>
<div class="grundsatz">
<span>ℹ</span>
<div>Der Termin ist eine <b>Erinnerung</b>. Zum geplanten Zeitpunkt passiert
nichts von selbst — das System meldet sich, beginnen musst du.</div>
</div>
</div>`,
bestaetigenText: "Planen",
bestaetigt: async (el, zu) => {
if (!gewaehlt) return meldung("Bitte zuerst einen Patienten wählen.", "fehler");
const saalFeld = el.querySelector("[name=saal]");
try {
const antwort = await aufruf("operation", "planen", {
patient: gewaehlt.id,
aufnahme: gewaehlt.aufnahme || null,
art: el.querySelector("[name=art]").value.trim(),
saal: saalFeld.value ? +saalFeld.value : null,
vorlaufMinuten: +el.querySelector("[name=vorlauf]").value,
selbstOperateur: el.querySelector("[name=operateur]").checked,
});
zu();
meldung("Operation " + antwort.kennung + " geplant.", "gut");
oeffne(behaelter, antwort.id);
} 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("operation", "patientSuche", { begriff });
const treffer = antwort.treffer || [];
saele = antwort.saele || [];
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.allergien ? `<span class="marker warn">A</span>` : ""}
${t.aufnahme ? `<span class="marker info">im Haus</span>` : ""}
</div>`).join("")
: `<div class="liste-leer">Keine Person gefunden.</div>`;
liste.querySelectorAll("[data-person]").forEach((el) => {
el.addEventListener("click", () => {
gewaehlt = treffer.find((t) => t.id === +el.dataset.person);
liste.querySelectorAll("[data-person]")
.forEach((zeile) => zeile.classList.remove("gewaehlt"));
el.classList.add("gewaehlt");
formular.style.display = "";
hg.querySelector("[name=saal]").innerHTML =
`<option value="">— später festlegen —</option>` +
saele.map((s) =>
`<option value="${s.id}" ${s.status === "gesperrt" ? "disabled" : ""}>
${sicher(s.kennung)}${s.belegt > 0 ? " (gerade belegt)" : ""}
${s.status === "gesperrt" ? " — gesperrt" : ""}</option>`).join("");
});
});
} catch (f) { meldung(f.message, "fehler"); }
}, 260);
});
},
});
}
function umplanungsFenster(behaelter, d) {
const o = d.operation;
bestaetigen({
titel: "Operation umplanen",
inhalt: `
<p><b>${sicher(o.kennung)}</b> — ${sicher(o.art)}</p>
<div class="feld-reihe">
<div class="feld"><label>OP-Saal</label>
<select name="saal">
<option value="">— kein Saal —</option>
${d.saele.map((s) =>
`<option value="${s.id}" ${s.id === o.saal ? "selected" : ""}
${s.status === "gesperrt" ? "disabled" : ""}>
${sicher(s.kennung)}${s.status === "gesperrt" ? " — gesperrt" : ""}
</option>`).join("")}
</select></div>
<div class="feld"><label>Neuer Beginn in … Minuten</label>
<input name="vorlauf" type="number" min="0" max="20160" value="30">
<div class="feld-hinweis">0 = Termin entfernen</div></div>
</div>
<div class="feld"><label>Begründung (Pflicht)</label>
<input name="grund" maxlength="255"
placeholder="Warum wird verschoben?"></div>`,
bestaetigenText: "Umplanen",
bestaetigt: async (el, zu) => {
const grund = el.querySelector("[name=grund]").value.trim();
if (grund.length < 3) return meldung("Bitte eine Begründung angeben.", "fehler");
const saalFeld = el.querySelector("[name=saal]");
try {
await aufruf("operation", "umplanen", {
id: o.id,
saal: saalFeld.value ? +saalFeld.value : null,
vorlaufMinuten: +el.querySelector("[name=vorlauf]").value,
grund,
});
zu();
meldung("Umgeplant.", "gut");
oeffne(behaelter, o.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
}
/* ---- Laden ------------------------------------------------------------------- */
async function oeffne(behaelter, operationId) {
try {
const d = await aufruf("operation", "oeffnen", { id: operationId });
z.offen = operationId;
einzelAnsicht(behaelter, d);
} catch (f) { meldung(f.message, "fehler"); }
}
async function rendern(behaelter) {
try {
z.daten = await aufruf("operation", "uebersicht", { ordner: z.ordner });
// Sprungziel aus einem Hinweis, z. B. „Operation beginnt in 10 Minuten"
const ziel = App.zustand.modulZiel;
if (ziel && ziel.operation) {
App.zustand.modulZiel = null;
return oeffne(behaelter, ziel.operation);
}
if (z.offen) await oeffne(behaelter, z.offen);
else uebersicht(behaelter);
} catch (f) {
behaelter.innerHTML = `<div class="seiten-kopf"><div><h2>Operationen</h2>
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
}
}
App.registriereAnsicht("operation", {
rendern,
aktualisieren() {
if (!z.offen && App.zustand.aktivesModul === "operation") rendern(App.$("inhalt"));
},
});
App.registriereSuche(async (begriff) => {
try {
const d = await aufruf("operation", "uebersicht", { ordner: "plan" });
const klein = begriff.toLowerCase();
return (d.liste || [])
.filter((o) => (o.kennung + " " + o.art + " " + o.patientName)
.toLowerCase().includes(klein))
.slice(0, 5)
.map((o) => ({
bereich: "Operation", titel: o.kennung + " · " + o.art,
unter: o.patientName + " · " + o.status,
}));
} catch (e) { return []; }
});
})();