/* ===================================================================== aether-medical / Ansicht: Medizinisches MDT Patientensuche und Patientenakte. Alles, was hier rot oder auffällig dargestellt wird, hat ein Mediziner eingetragen — das System leitet aus keinem Feld eine Warnung, eine Diagnose oder eine Empfehlung ab. ===================================================================== */ "use strict"; (() => { const { sicher, zeit, aufruf, meldung, fenster, bestaetigen } = App; const z = { suche: "", treffer: [], patient: null, // geöffnete Patientenakte (Serverantwort) reiter: "stamm", // stamm | verlauf | akten | notizen unterdaten: null, // Daten des aktiven Reiters offeneAkte: null, }; // Symbol und Farbe je Verlaufsart const verlaufsArt = { aufnahme: { symbol: "🏥", art: "info" }, entlassung: { symbol: "🚪", art: "neutral" }, behandlung: { symbol: "🩺", art: "gut" }, operation: { symbol: "🔪", art: "warn" }, labor: { symbol: "🧪", art: "info" }, medikament: { symbol: "💊", art: "neutral" }, notiz: { symbol: "📝", art: "neutral" }, }; const statusArt = { entwurf: "neutral", offen: "warn", freigegeben: "gut", geschlossen: "info", }; /* ---- Suche ------------------------------------------------------------ */ function sucheAnsicht(behaelter) { behaelter.innerHTML = `

Medizinisches MDT

Patientensuche über Name, Geburtsdatum oder Telefonnummer. Was in einer Akte steht, hat ein Mediziner eingetragen.

Treffer ${z.treffer.length} Personen
${z.treffer.length ? z.treffer.map(trefferZeile).join("") : `
${z.suche.length >= 2 ? "Keine Person gefunden." : "Mindestens zwei Zeichen eingeben."}
`}
`; const feld = behaelter.querySelector("[data-suche]"); let warten = null; feld.addEventListener("input", () => { clearTimeout(warten); warten = setTimeout(async () => { z.suche = feld.value.trim(); if (z.suche.length < 2) { z.treffer = []; return sucheAnsicht(behaelter); } try { const d = await aufruf("mdt", "suche", { begriff: z.suche }); z.treffer = d.treffer || []; sucheAnsicht(behaelter); // Den Fokus zurück ins Suchfeld, damit man weitertippen kann const neu = behaelter.querySelector("[data-suche]"); neu.focus(); neu.setSelectionRange(neu.value.length, neu.value.length); } catch (f) { meldung(f.message, "fehler"); } }, 280); }); behaelter.querySelectorAll("[data-patient]").forEach((el) => { el.addEventListener("click", () => oeffnePatient(behaelter, +el.dataset.patient)); }); } function trefferZeile(p) { return `
${p.lichtbild ? `` : `—`}
${sicher(p.firstname)} ${sicher(p.lastname)}
geb. ${sicher(p.dob)} ${p.telefon ? " · ☎ " + sicher(p.telefon) : ""} ${p.blutgruppe ? " · " + sicher(p.blutgruppe) : " · Blutgruppe unbekannt"}
${p.hinweis ? `Warnhinweis` : ""} ${p.allergien ? `Allergien` : ""} ${p.imHaus ? `im Haus` : ""} ${p.wartet ? `wartet` : ""}
`; } /* ---- Patientenakte ------------------------------------------------------ */ function akteAnsicht(behaelter) { const d = z.patient; const p = d.patient, s = d.stamm; behaelter.innerHTML = `

${sicher(p.name)}

geb. ${sicher(p.geburt)} · ${sicher(p.geschlecht === "w" ? "weiblich" : "männlich")} ${p.telefon ? " · ☎ " + sicher(p.telefon) : ""}

${s.hinweis ? `
⚠
Warnhinweis — ${sicher(s.hinweis)} ${s.gepflegtVon ? `
Eingetragen von ${sicher(s.gepflegtVon)}${s.gepflegtAm ? " am " + zeit(s.gepflegtAm) : ""}. Das System hat diesen Hinweis nicht erzeugt.` : ""}
` : ""} ${d.aufnahme ? `
🏥
${d.aufnahme.status === "wartend" ? "Wartet auf Aufnahme" : "Aufgenommen"} — ${sicher(d.aufnahme.grund)} ${d.aufnahme.zimmer ? " · Zimmer " + sicher(d.aufnahme.zimmer) : ""}
Seit ${zeit(d.aufnahme.aufgenommen_am)} durch ${sicher(d.aufnahme.aufgenommenVon)}.
` : ""} ${d.intensiv ? `
🫀
Auf Intensivstation — Zustand als ${sicher(d.intensiv.zustand)} eingetragen ${d.intensiv.verantwortlichName ? " · zuständig: " + sicher(d.intensiv.verantwortlichName) : ""}
` : ""}
${s.lichtbild ? `Lichtbild` : `Kein Lichtbild`}
${wertZeile("Blutgruppe", s.blutgruppe, !s.blutgruppe && "nicht bestimmt")} ${wertZeile("Allergien", s.allergien, "keine erfasst")} ${wertZeile("Vorerkrankungen", s.vorerkrankungen, "keine erfasst")} ${wertZeile("Dauermedikation", s.dauermedikation, "keine erfasst")} ${wertZeile("Implantate", s.implantate, "keine erfasst")} ${wertZeile("Organspende", s.organspenderName)} ${wertZeile("Notfallkontakt", s.notfallkontakt, "keiner hinterlegt")}
${d.rechte.stammdaten ? `` : ""} ${d.rechte.hinweis ? `` : ""}
Lade …
`; verdrahteAkte(behaelter); zeichneReiter(behaelter); } /** Eine Zeile im Stammdatenblock */ function wertZeile(name, wert, ersatz) { const fehlt = !wert; return `
${sicher(name)} ${fehlt ? sicher(ersatz || "nicht erfasst") : sicher(wert)}
`; } function verdrahteAkte(behaelter) { behaelter.querySelector("[data-zurueck]").addEventListener("click", () => { z.patient = null; z.offeneAkte = null; sucheAnsicht(behaelter); }); behaelter.querySelectorAll("[data-reiter]").forEach((el) => { el.addEventListener("click", () => { z.reiter = el.dataset.reiter; z.offeneAkte = null; akteAnsicht(behaelter); }); }); const stamm = behaelter.querySelector("[data-stamm]"); if (stamm) stamm.addEventListener("click", () => stammdatenFenster(behaelter)); const hinweis = behaelter.querySelector("[data-hinweis]"); if (hinweis) hinweis.addEventListener("click", () => hinweisFenster(behaelter)); } /* ---- Reiterinhalte ------------------------------------------------------- */ async function zeichneReiter(behaelter) { const ziel = behaelter.querySelector("#mdt-unterinhalt"); const patientId = z.patient.patient.id; try { if (z.reiter === "verlauf") { z.unterdaten = await aufruf("mdt", "verlauf", { patient: patientId }); zeichneVerlauf(ziel); } else if (z.reiter === "akten") { z.unterdaten = await aufruf("mdt", "akten", { patient: patientId }); zeichneAkten(ziel, behaelter); } else if (z.reiter === "notizen") { z.unterdaten = await aufruf("mdt", "notizen", { patient: patientId }); zeichneNotizen(ziel, behaelter); } else { zeichneUebersicht(ziel); } } catch (f) { ziel.innerHTML = `
${sicher(f.message)}
`; } } function zeichneUebersicht(ziel) { const d = z.patient; const zaehler = d.zaehler; ziel.innerHTML = `
Behandlungen
${zaehler.behandlungen}
dokumentiert
Operationen
${zaehler.operationen}
dokumentiert
Laboraufträge
${zaehler.labor}
gestellt
Aufnahmen
${zaehler.aufnahmen}
insgesamt
Akten
${zaehler.akten}
angelegt
Vermerke
${zaehler.notizen}
geschrieben
Fehlende Angaben bedeuten „nicht erhoben", nicht „unauffällig". Eine leere Allergieliste heißt, dass niemand danach gefragt oder es eingetragen hat. Das System füllt keines dieser Felder von selbst und schließt aus keinem etwas.
`; } function zeichneVerlauf(ziel) { const eintraege = z.unterdaten.eintraege || []; ziel.innerHTML = eintraege.length ? `
Zeitleiste ${z.unterdaten.gesamt} Einträge insgesamt
${eintraege.map((e) => { const stil = verlaufsArt[e.art] || { symbol: "•", art: "neutral" }; return `
${stil.symbol}
${sicher(e.titel)} ${zeit(e.am)}
${e.text ? `
${sicher(e.text)}
` : ""} ${e.marker ? `
${sicher(e.marker)}
` : ""}
`; }).join("")}
` : `
Für diesen Patienten ist noch nichts dokumentiert.
`; } function zeichneAkten(ziel, behaelter) { const d = z.unterdaten; // Eine einzelne Akte ist geöffnet if (z.offeneAkte) return zeichneEinzelakte(ziel, behaelter); ziel.innerHTML = `
Akten dieses Patienten ${z.patient.rechte.akteErstellen ? `` : ""}
${d.akten.length ? d.akten.map((a) => `
${sicher(a.aktenzeichen)}
${sicher(a.titel)}
${sicher(a.typName)} · ${sicher(a.autor)} · ${zeit(a.erstellt_am)}
v${a.version} ${sicher(a.status)}
`).join("") : `
Noch keine Akte angelegt.
`}
`; ziel.querySelectorAll("[data-akte]").forEach((el) => { el.addEventListener("click", async () => { try { z.offeneAkte = await aufruf("mdt", "akteOeffnen", { id: +el.dataset.akte }); zeichneEinzelakte(ziel, behaelter); } catch (f) { meldung(f.message, "fehler"); } }); }); const neu = ziel.querySelector("[data-neu]"); if (neu) neu.addEventListener("click", () => akteAnlegenFenster(behaelter)); } function zeichneEinzelakte(ziel, behaelter) { const d = z.offeneAkte; const a = d.akte; ziel.innerHTML = `
${sicher(a.aktenzeichen)} · Version ${a.version}
${d.rechte.bearbeiten && a.status !== "geschlossen" ? `` : ""} ${d.rechte.freigeben && a.status !== "freigegeben" && a.status !== "geschlossen" ? `` : ""} ${d.rechte.schliessen && a.status !== "geschlossen" ? `` : ""}

${sicher(a.titel)}

${sicher(a.status)} Angelegt von ${sicher(a.autor)} am ${zeit(a.erstelltAm)} ${a.freigabeVon ? ` · freigegeben von ${sicher(a.freigabeVon)} am ${zeit(a.freigabeAm)}` : ""}
${a.inhalt ? sicher(a.inhalt).replace(/\n/g, "
") : `Diese Akte hat noch keinen Inhalt.`}
Versionsverlauf Nichts wird überschrieben
${d.versionen.map((v) => `
v${v.version}
${sicher(v.kommentar || "ohne Kommentar")}
${sicher(v.autor)} · ${zeit(v.geaendert_am)}
${v.version === a.version ? `aktuell` : `ansehen`}
`).join("")}
`; ziel.querySelector("[data-zurueckliste]").addEventListener("click", () => { z.offeneAkte = null; zeichneReiter(behaelter); }); const bearbeiten = ziel.querySelector("[data-bearbeiten]"); if (bearbeiten) bearbeiten.addEventListener("click", () => akteBearbeitenFenster(behaelter)); ziel.querySelectorAll("[data-status]").forEach((el) => { el.addEventListener("click", () => akteStatusFenster(behaelter, el.dataset.status)); }); ziel.querySelectorAll("[data-version]").forEach((el) => { el.addEventListener("click", () => versionAnsehen(+el.dataset.version)); }); } function zeichneNotizen(ziel, behaelter) { const notizen = z.unterdaten.notizen || []; ziel.innerHTML = `
Vermerke
${notizen.length ? notizen.map((n) => `
${sicher(n.text)}
${sicher(n.autor)} ${n.personalnummer ? " (" + sicher(n.personalnummer) + ")" : ""} · ${zeit(n.am)}
${n.eigene ? `von dir` : ""}
`).join("") : `
Keine Vermerke.
`}
`; ziel.querySelector("[data-neu-notiz]").addEventListener("click", () => { fenster({ titel: "Vermerk anlegen", inhalt: `
Ein Vermerk ist keine Diagnose und ersetzt keine Behandlungsdokumentation.
`, knoepfe: [ { text: "Abbrechen", art: "leise", klick: (_, zu) => zu() }, { text: "Speichern", klick: async (el, zu) => { try { await aufruf("mdt", "notizAnlegen", { patient: z.patient.patient.id, text: el.querySelector("[name=text]").value.trim(), }); zu(); meldung("Vermerk gespeichert.", "gut"); oeffnePatient(behaelter, z.patient.patient.id, "notizen"); } catch (f) { meldung(f.message, "fehler"); } } }, ], }); }); } /* ---- Dialoge -------------------------------------------------------------- */ function stammdatenFenster(behaelter) { const d = z.patient; const s = d.stamm; fenster({ titel: "Stammdaten pflegen", inhalt: `
Wird nicht aus dem Labor übernommen — die Feststellung verantwortet ein Mediziner.
`, knoepfe: [ { text: "Abbrechen", art: "leise", klick: (_, zu) => zu() }, { text: "Speichern", klick: async (el, zu) => { const wert = (name) => el.querySelector(`[name=${name}]`).value.trim(); try { await aufruf("mdt", "stammdaten", { patient: d.patient.id, blutgruppe: wert("blutgruppe"), organspender: wert("organspender"), allergien: wert("allergien"), vorerkrankungen: wert("vorerkrankungen"), dauermedikation: wert("dauermedikation"), implantate: wert("implantate"), notfallkontakt: wert("notfallkontakt"), lichtbild: wert("lichtbild"), }); zu(); meldung("Stammdaten gespeichert.", "gut"); oeffnePatient(behaelter, d.patient.id, z.reiter); } catch (f) { meldung(f.message, "fehler"); } } }, ], }); } function hinweisFenster(behaelter) { const d = z.patient; bestaetigen({ titel: d.stamm.hinweis ? "Warnhinweis ändern" : "Warnhinweis setzen", inhalt: `

Ein Warnhinweis erscheint bei jeder Suche und ganz oben in der Akte. Er ist für Dinge gedacht, die vor jeder Maßnahme bekannt sein müssen.

`, bestaetigenText: "Übernehmen", bestaetigt: async (el, zu) => { try { await aufruf("mdt", "hinweisSetzen", { patient: d.patient.id, hinweis: el.querySelector("[name=hinweis]").value.trim(), grund: el.querySelector("[name=grund]").value.trim(), }); zu(); meldung("Warnhinweis übernommen.", "gut"); oeffnePatient(behaelter, d.patient.id, z.reiter); } catch (f) { meldung(f.message, "fehler"); } }, }); } function akteAnlegenFenster(behaelter) { const arten = z.unterdaten.aktenarten || []; fenster({ titel: "Akte anlegen", inhalt: `
Die Akte startet als Entwurf. Jede spätere Änderung erzeugt eine neue Version — überschrieben wird nichts.
`, knoepfe: [ { text: "Abbrechen", art: "leise", klick: (_, zu) => zu() }, { text: "Anlegen", klick: async (el, zu) => { try { const antwort = await aufruf("mdt", "akteAnlegen", { patient: z.patient.patient.id, titel: el.querySelector("[name=titel]").value.trim(), typ: el.querySelector("[name=typ]").value, inhalt: el.querySelector("[name=inhalt]").value, }); zu(); meldung("Akte " + antwort.aktenzeichen + " angelegt.", "gut"); oeffnePatient(behaelter, z.patient.patient.id, "akten"); } catch (f) { meldung(f.message, "fehler"); } } }, ], }); } function akteBearbeitenFenster(behaelter) { const a = z.offeneAkte.akte; fenster({ titel: "Akte bearbeiten · " + a.aktenzeichen, inhalt: `
Der Kommentar steht dauerhaft im Versionsverlauf. Version ${a.version} bleibt unverändert erhalten.
`, knoepfe: [ { text: "Abbrechen", art: "leise", klick: (_, zu) => zu() }, { text: "Neue Version speichern", klick: async (el, zu) => { const kommentar = el.querySelector("[name=kommentar]").value.trim(); if (kommentar.length < 3) { return meldung("Bitte angeben, was geändert wurde.", "fehler"); } try { await aufruf("mdt", "akteSpeichern", { id: a.id, titel: el.querySelector("[name=titel]").value.trim(), inhalt: el.querySelector("[name=inhalt]").value, kommentar, }); zu(); meldung("Neue Version gespeichert.", "gut"); z.offeneAkte = await aufruf("mdt", "akteOeffnen", { id: a.id }); zeichneEinzelakte(behaelter.querySelector("#mdt-unterinhalt"), behaelter); } catch (f) { meldung(f.message, "fehler"); } } }, ], }); } function akteStatusFenster(behaelter, status) { const a = z.offeneAkte.akte; const beschreibung = { freigegeben: "Die Akte gilt damit als geprüft und freigegeben. Sie lässt sich weiterhin bearbeiten.", geschlossen: "Eine geschlossene Akte lässt sich nicht mehr ändern. Der Inhalt bleibt lesbar.", }; bestaetigen({ titel: status === "freigegeben" ? "Akte freigeben" : "Akte schließen", gefahr: status === "geschlossen", inhalt: `

${sicher(a.aktenzeichen)} — ${sicher(a.titel)}

${beschreibung[status] || ""}

`, bestaetigenText: status === "freigegeben" ? "Freigeben" : "Schließen", bestaetigt: async (el, zu) => { try { await aufruf("mdt", "akteStatus", { id: a.id, status, grund: el.querySelector("[name=grund]").value.trim(), }); zu(); meldung("Status geändert.", "gut"); z.offeneAkte = await aufruf("mdt", "akteOeffnen", { id: a.id }); zeichneEinzelakte(behaelter.querySelector("#mdt-unterinhalt"), behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); } async function versionAnsehen(version) { try { const v = await aufruf("mdt", "akteVersion", { id: z.offeneAkte.akte.id, version }); fenster({ titel: `Version ${v.version} · ${z.offeneAkte.akte.aktenzeichen}`, inhalt: `

${sicher(v.autor)} · ${zeit(v.geaendert_am)} ${v.kommentar ? " · " + sicher(v.kommentar) : ""}

${v.inhalt ? sicher(v.inhalt).replace(/\n/g, "
") : `Diese Version hatte keinen Inhalt.`}
`, }); } catch (f) { meldung(f.message, "fehler"); } } /* ---- Laden ----------------------------------------------------------------- */ async function oeffnePatient(behaelter, patientId, reiter) { try { z.patient = await aufruf("mdt", "oeffnen", { patient: patientId }); z.reiter = reiter || "stamm"; z.offeneAkte = null; akteAnsicht(behaelter); } catch (f) { meldung(f.message, "fehler"); } } async function rendern(behaelter) { // Sprungziel aus einem anderen Modul oder aus einem Hinweis const ziel = App.zustand.modulZiel; if (ziel && ziel.patient) { App.zustand.modulZiel = null; return oeffnePatient(behaelter, ziel.patient); } if (ziel && ziel.akte) { App.zustand.modulZiel = null; try { const akte = await aufruf("mdt", "akteOeffnen", { id: ziel.akte }); if (akte.akte.patient) { await oeffnePatient(behaelter, akte.akte.patient, "akten"); z.offeneAkte = akte; zeichneEinzelakte(behaelter.querySelector("#mdt-unterinhalt"), behaelter); return; } } catch (f) { meldung(f.message, "fehler"); } } if (z.patient) akteAnsicht(behaelter); else sucheAnsicht(behaelter); } App.registriereAnsicht("mdt", { rendern }); App.registriereSuche(async (begriff) => { try { const d = await aufruf("mdt", "suche", { begriff }); return (d.treffer || []).slice(0, 5).map((p) => ({ bereich: "Patient", titel: p.firstname + " " + p.lastname, unter: "geb. " + p.dob + (p.blutgruppe ? " · " + p.blutgruppe : ""), })); } catch (e) { return []; } }); })();