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

767 lines
31 KiB
JavaScript
Raw 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: 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 = `
<div class="seiten-kopf">
<div>
<h2>Medizinisches MDT</h2>
<p>Patientensuche über Name, Geburtsdatum oder Telefonnummer.
Was in einer Akte steht, hat ein Mediziner eingetragen.</p>
</div>
</div>
<div class="filterleiste">
<input data-suche="1" placeholder="Name, Geburtsdatum (JJJJ-MM-TT) oder Telefonnummer…"
value="${sicher(z.suche)}" autofocus>
</div>
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Treffer</span>
<span class="feld-hinweis">${z.treffer.length} Personen</span>
</div>
<div class="liste">
${z.treffer.length ? z.treffer.map(trefferZeile).join("")
: `<div class="liste-leer">${z.suche.length >= 2
? "Keine Person gefunden."
: "Mindestens zwei Zeichen eingeben."}</div>`}
</div>
</div>
`;
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 `<div class="liste-zeile" data-patient="${p.id}">
<div class="mdt-bild klein">${p.lichtbild
? `<img src="${sicher(p.lichtbild)}" alt="">`
: `<span>—</span>`}</div>
<div class="lz-haupt">
<div class="lz-titel">${sicher(p.firstname)} ${sicher(p.lastname)}</div>
<div class="lz-unter">
geb. ${sicher(p.dob)}
${p.telefon ? " · ☎ " + sicher(p.telefon) : ""}
${p.blutgruppe ? " · " + sicher(p.blutgruppe) : " · Blutgruppe unbekannt"}
</div>
</div>
${p.hinweis ? `<span class="marker kritisch" title="${sicher(p.hinweis)}">Warnhinweis</span>` : ""}
${p.allergien ? `<span class="marker warn" title="${sicher(p.allergien)}">Allergien</span>` : ""}
${p.imHaus ? `<span class="marker info">im Haus</span>` : ""}
${p.wartet ? `<span class="marker warn">wartet</span>` : ""}
</div>`;
}
/* ---- Patientenakte ------------------------------------------------------ */
function akteAnsicht(behaelter) {
const d = z.patient;
const p = d.patient, s = d.stamm;
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>${sicher(p.name)}</h2>
<p>geb. ${sicher(p.geburt)} · ${sicher(p.geschlecht === "w" ? "weiblich" : "männlich")}
${p.telefon ? " · ☎ " + sicher(p.telefon) : ""}</p>
</div>
<button class="knopf leise" data-zurueck="1">← Suche</button>
</div>
${s.hinweis ? `
<div class="warnung kritisch">
<span class="w-symbol">⚠</span>
<div><b>Warnhinweis</b> — ${sicher(s.hinweis)}
${s.gepflegtVon ? `<br><span class="feld-hinweis">Eingetragen von
${sicher(s.gepflegtVon)}${s.gepflegtAm ? " am " + zeit(s.gepflegtAm) : ""}.
Das System hat diesen Hinweis nicht erzeugt.</span>` : ""}
</div>
</div>` : ""}
${d.aufnahme ? `
<div class="warnung ${d.aufnahme.status === "wartend" ? "warn" : ""}">
<span class="w-symbol">🏥</span>
<div><b>${d.aufnahme.status === "wartend"
? "Wartet auf Aufnahme" : "Aufgenommen"}</b>
— ${sicher(d.aufnahme.grund)}
${d.aufnahme.zimmer ? " · Zimmer " + sicher(d.aufnahme.zimmer) : ""}
<br><span class="feld-hinweis">Seit ${zeit(d.aufnahme.aufgenommen_am)}
durch ${sicher(d.aufnahme.aufgenommenVon)}.</span>
</div>
</div>` : ""}
${d.intensiv ? `
<div class="warnung ${d.intensiv.zustand === "kritisch" ? "kritisch" : ""}">
<span class="w-symbol">🫀</span>
<div><b>Auf Intensivstation</b> — Zustand als
<b>${sicher(d.intensiv.zustand)}</b> eingetragen
${d.intensiv.verantwortlichName
? " · zuständig: " + sicher(d.intensiv.verantwortlichName) : ""}
</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">
${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")}
</div>
<div class="mdt-kopf-aktionen">
${d.rechte.stammdaten
? `<button class="knopf klein" data-stamm="1">Stammdaten pflegen</button>` : ""}
${d.rechte.hinweis
? `<button class="knopf leise klein" data-hinweis="1">
${s.hinweis ? "Warnhinweis ändern" : "Warnhinweis setzen"}</button>` : ""}
</div>
</div>
<div class="reiter">
<button class="${z.reiter === "stamm" ? "aktiv" : ""}" data-reiter="stamm">
Übersicht</button>
<button class="${z.reiter === "verlauf" ? "aktiv" : ""}" data-reiter="verlauf">
Verlauf</button>
<button class="${z.reiter === "akten" ? "aktiv" : ""}" data-reiter="akten">
Akten <span class="mdt-zahl">${d.zaehler.akten}</span></button>
<button class="${z.reiter === "notizen" ? "aktiv" : ""}" data-reiter="notizen">
Vermerke <span class="mdt-zahl">${d.zaehler.notizen}</span></button>
</div>
<div id="mdt-unterinhalt">
<div class="laden"><div class="spinner"></div><span>Lade …</span></div>
</div>
`;
verdrahteAkte(behaelter);
zeichneReiter(behaelter);
}
/** Eine Zeile im Stammdatenblock */
function wertZeile(name, wert, ersatz) {
const fehlt = !wert;
return `<div class="mdt-wert ${fehlt ? "fehlt" : ""}">
<span>${sicher(name)}</span>
<b>${fehlt ? sicher(ersatz || "nicht erfasst") : sicher(wert)}</b>
</div>`;
}
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 = `<div class="liste-leer">${sicher(f.message)}</div>`;
}
}
function zeichneUebersicht(ziel) {
const d = z.patient;
const zaehler = d.zaehler;
ziel.innerHTML = `
<div class="raster k3">
<div class="kennzahl">
<div class="kz-titel">Behandlungen</div>
<div class="kz-wert">${zaehler.behandlungen}</div>
<div class="kz-zusatz">dokumentiert</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Operationen</div>
<div class="kz-wert">${zaehler.operationen}</div>
<div class="kz-zusatz">dokumentiert</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Laboraufträge</div>
<div class="kz-wert">${zaehler.labor}</div>
<div class="kz-zusatz">gestellt</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Aufnahmen</div>
<div class="kz-wert">${zaehler.aufnahmen}</div>
<div class="kz-zusatz">insgesamt</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Akten</div>
<div class="kz-wert">${zaehler.akten}</div>
<div class="kz-zusatz">angelegt</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Vermerke</div>
<div class="kz-wert">${zaehler.notizen}</div>
<div class="kz-zusatz">geschrieben</div>
</div>
</div>
<div class="db-grundsatz">
<b>Fehlende Angaben bedeuten „nicht erhoben", nicht „unauffällig".</b>
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.
</div>
`;
}
function zeichneVerlauf(ziel) {
const eintraege = z.unterdaten.eintraege || [];
ziel.innerHTML = eintraege.length ? `
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Zeitleiste</span>
<span class="feld-hinweis">${z.unterdaten.gesamt} Einträge insgesamt</span>
</div>
<div class="mdt-verlauf">
${eintraege.map((e) => {
const stil = verlaufsArt[e.art] || { symbol: "•", art: "neutral" };
return `<div class="mdt-vl-schritt">
<div class="mdt-vl-punkt ${sicher(stil.art)}">${stil.symbol}</div>
<div class="mdt-vl-inhalt">
<div class="mdt-vl-kopf">
<b>${sicher(e.titel)}</b>
<span class="db-zeit">${zeit(e.am)}</span>
</div>
${e.text ? `<div class="mdt-vl-text">${sicher(e.text)}</div>` : ""}
${e.marker ? `<div class="mdt-vl-meta">${sicher(e.marker)}</div>` : ""}
</div>
</div>`;
}).join("")}
</div>
</div>`
: `<div class="liste-leer">Für diesen Patienten ist noch nichts dokumentiert.</div>`;
}
function zeichneAkten(ziel, behaelter) {
const d = z.unterdaten;
// Eine einzelne Akte ist geöffnet
if (z.offeneAkte) return zeichneEinzelakte(ziel, behaelter);
ziel.innerHTML = `
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Akten dieses Patienten</span>
${z.patient.rechte.akteErstellen
? `<button class="knopf klein" data-neu="1">+ Akte anlegen</button>` : ""}
</div>
<div class="liste">
${d.akten.length ? d.akten.map((a) => `
<div class="liste-zeile" data-akte="${a.id}">
<span class="db-nummer">${sicher(a.aktenzeichen)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(a.titel)}</div>
<div class="lz-unter">${sicher(a.typName)} · ${sicher(a.autor)}
· ${zeit(a.erstellt_am)}</div>
</div>
<span class="marker neutral">v${a.version}</span>
<span class="marker ${statusArt[a.status] || "neutral"}">${sicher(a.status)}</span>
</div>`).join("")
: `<div class="liste-leer">Noch keine Akte angelegt.</div>`}
</div>
</div>`;
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 = `
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">${sicher(a.aktenzeichen)} · Version ${a.version}</span>
<div class="mdt-akte-aktionen">
${d.rechte.bearbeiten && a.status !== "geschlossen"
? `<button class="knopf leise klein" data-bearbeiten="1">Bearbeiten</button>` : ""}
${d.rechte.freigeben && a.status !== "freigegeben" && a.status !== "geschlossen"
? `<button class="knopf leise klein" data-status="freigegeben">Freigeben</button>` : ""}
${d.rechte.schliessen && a.status !== "geschlossen"
? `<button class="knopf leise klein" data-status="geschlossen">Schließen</button>` : ""}
<button class="knopf leise klein" data-zurueckliste="1">← Akten</button>
</div>
</div>
<h3 class="mdt-akte-titel">${sicher(a.titel)}</h3>
<div class="mdt-akte-meta">
<span class="marker ${statusArt[a.status] || "neutral"}">${sicher(a.status)}</span>
<span class="feld-hinweis">Angelegt von ${sicher(a.autor)} am ${zeit(a.erstelltAm)}
${a.freigabeVon ? ` · freigegeben von ${sicher(a.freigabeVon)}
am ${zeit(a.freigabeAm)}` : ""}</span>
</div>
<div class="mdt-akte-inhalt">${a.inhalt
? sicher(a.inhalt).replace(/\n/g, "<br>")
: `<span class="feld-hinweis">Diese Akte hat noch keinen Inhalt.</span>`}</div>
</div>
<div class="karte" style="margin-top:14px">
<div class="karte-kopf">
<span class="karte-titel">Versionsverlauf</span>
<span class="feld-hinweis">Nichts wird überschrieben</span>
</div>
<div class="liste">
${d.versionen.map((v) => `
<div class="liste-zeile klickbar" data-version="${v.version}">
<span class="marker ${v.version === a.version ? "gut" : "neutral"}">
v${v.version}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(v.kommentar || "ohne Kommentar")}</div>
<div class="lz-unter">${sicher(v.autor)} · ${zeit(v.geaendert_am)}</div>
</div>
${v.version === a.version
? `<span class="feld-hinweis">aktuell</span>`
: `<span class="feld-hinweis">ansehen</span>`}
</div>`).join("")}
</div>
</div>
`;
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 = `
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Vermerke</span>
<button class="knopf klein" data-neu-notiz="1">+ Vermerk</button>
</div>
<div class="liste">
${notizen.length ? notizen.map((n) => `
<div class="liste-zeile">
<div class="lz-haupt">
<div class="lz-titel">${sicher(n.text)}</div>
<div class="lz-unter">${sicher(n.autor)}
${n.personalnummer ? " (" + sicher(n.personalnummer) + ")" : ""}
· ${zeit(n.am)}</div>
</div>
${n.eigene ? `<span class="marker neutral">von dir</span>` : ""}
</div>`).join("")
: `<div class="liste-leer">Keine Vermerke.</div>`}
</div>
</div>`;
ziel.querySelector("[data-neu-notiz]").addEventListener("click", () => {
fenster({
titel: "Vermerk anlegen",
inhalt: `<div class="feld"><label>Vermerk</label>
<textarea name="text" rows="4"
placeholder="Kurzer Hinweis zum Patienten…"></textarea>
<div class="feld-hinweis">Ein Vermerk ist keine Diagnose und ersetzt
keine Behandlungsdokumentation.</div></div>`,
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: `
<div class="feld-reihe">
<div class="feld"><label>Blutgruppe</label>
<select name="blutgruppe">
<option value="">— nicht bestimmt —</option>
${d.blutgruppen.map((b) =>
`<option value="${sicher(b)}" ${b === s.blutgruppe ? "selected" : ""}>
${sicher(b)}</option>`).join("")}
</select>
<div class="feld-hinweis">Wird nicht aus dem Labor übernommen — die
Feststellung verantwortet ein Mediziner.</div></div>
<div class="feld"><label>Organspende</label>
<select name="organspender">
${Object.keys(d.organstatus).map((k) =>
`<option value="${sicher(k)}" ${k === s.organspender ? "selected" : ""}>
${sicher(d.organstatus[k])}</option>`).join("")}
</select></div>
</div>
<div class="feld"><label>Allergien</label>
<textarea name="allergien" rows="2"
placeholder="z. B. Penicillin, Latex">${sicher(s.allergien || "")}</textarea></div>
<div class="feld"><label>Vorerkrankungen</label>
<textarea name="vorerkrankungen" rows="3">${sicher(s.vorerkrankungen || "")}</textarea></div>
<div class="feld"><label>Dauermedikation</label>
<textarea name="dauermedikation" rows="3">${sicher(s.dauermedikation || "")}</textarea></div>
<div class="feld"><label>Implantate</label>
<textarea name="implantate" rows="2"
placeholder="z. B. Herzschrittmacher">${sicher(s.implantate || "")}</textarea></div>
<div class="feld"><label>Notfallkontakt</label>
<input name="notfallkontakt" maxlength="160"
value="${sicher(s.notfallkontakt || "")}"
placeholder="Name und Telefonnummer"></div>
<div class="feld"><label>Lichtbild (Bild-Adresse)</label>
<input name="lichtbild" value="${sicher(s.lichtbild || "")}"
placeholder="https://…"></div>`,
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: `
<p>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.</p>
<div class="feld"><label>Warnhinweis (leer lassen zum Entfernen)</label>
<textarea name="hinweis" rows="3"
placeholder="z. B. Gerinnungsstörung — keine Antikoagulantien">${sicher(d.stamm.hinweis || "")}</textarea></div>
<div class="feld"><label>Begründung</label>
<input name="grund" maxlength="255"
placeholder="Woher stammt die Information?"></div>`,
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: `
<div class="feld"><label>Titel</label>
<input name="titel" maxlength="160" placeholder="Worum geht es?"></div>
<div class="feld"><label>Aktenart</label>
<select name="typ">
${arten.map((a) =>
`<option value="${sicher(a.id)}">${sicher(a.name)}</option>`).join("")}
</select></div>
<div class="feld"><label>Inhalt</label>
<textarea name="inhalt" rows="10"
placeholder="Text der Akte…"></textarea>
<div class="feld-hinweis">Die Akte startet als Entwurf. Jede spätere Änderung
erzeugt eine neue Version — überschrieben wird nichts.</div></div>`,
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: `
<div class="feld"><label>Titel</label>
<input name="titel" maxlength="160" value="${sicher(a.titel)}"></div>
<div class="feld"><label>Inhalt</label>
<textarea name="inhalt" rows="12">${sicher(a.inhalt || "")}</textarea></div>
<div class="feld"><label>Was wurde geändert? (Pflicht)</label>
<input name="kommentar" maxlength="255"
placeholder="z. B. Befund vom Labor ergänzt">
<div class="feld-hinweis">Der Kommentar steht dauerhaft im Versionsverlauf.
Version ${a.version} bleibt unverändert erhalten.</div></div>`,
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 <b>nicht mehr ändern</b>. Der Inhalt bleibt lesbar.",
};
bestaetigen({
titel: status === "freigegeben" ? "Akte freigeben" : "Akte schließen",
gefahr: status === "geschlossen",
inhalt: `<p><b>${sicher(a.aktenzeichen)}</b> — ${sicher(a.titel)}</p>
<p>${beschreibung[status] || ""}</p>
<div class="feld"><label>Vermerk (freiwillig)</label>
<input name="grund" maxlength="255"></div>`,
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: `
<p class="feld-hinweis">${sicher(v.autor)} · ${zeit(v.geaendert_am)}
${v.kommentar ? " · " + sicher(v.kommentar) : ""}</p>
<div class="mdt-akte-inhalt">${v.inhalt
? sicher(v.inhalt).replace(/\n/g, "<br>")
: `<span class="feld-hinweis">Diese Version hatte keinen Inhalt.</span>`}</div>`,
});
} 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 []; }
});
})();