/* =====================================================================
aether-medical / Ansicht: Behandlungsverwaltung
Ein Dokumentationsbogen, mehr nicht. Kein Feld wird vorausgefüllt,
keine Diagnose vorgeschlagen, kein Symptom ausgewertet. Was hier
steht, hat ein Mediziner geschrieben.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen } = App;
const z = {
ordner: "laufend", // laufend | meine | abgeschlossen
suche: "",
daten: null,
offen: null,
offeneDaten: null,
};
/* ---- Übersicht --------------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
behaelter.innerHTML = `
Behandlungen
Das System speichert ausschließlich die Dokumentation. Es behandelt
nicht, es diagnostiziert nicht und es schlägt nichts vor.
${d.rechte.dokumentieren
? `
` : ""}
Laufend
${d.zaehler.laufend}
noch nicht abgeschlossen
Davon von dir
${d.zaehler.meine}
deine offene Dokumentation
Heute abgeschlossen
${d.zaehler.heute}
in der ganzen Klinik
Dokumentationen
${d.liste.length} Einträge
${d.liste.length ? d.liste.map(zeile).join("")
: `
Keine Dokumentation gefunden.
`}
`;
verdrahteUebersicht(behaelter);
}
function zeile(b) {
const laufend = b.status === "laufend";
return `
${sicher(b.kennung)}
${sicher(b.patientName)}
geb. ${sicher(b.geburt)}
${sicher(b.diagnose || b.massnahmen
|| "Noch nichts eingetragen")}
${b.hinweis ? `
⚠` : ""}
${b.allergien ? `
A` : ""}
${b.gaben > 0 ? `
💊 ${b.gaben}` : ""}
${b.einsatzNummer ? `
${sicher(b.einsatzNummer)}` : ""}
${b.eigene ? `
von dir` : ""}
${sicher(b.status)}
${laufend
? "offen seit " + minuten(b.offenSeit) : zeit(b.abgeschlossen_am)}
`;
}
function verdrahteUebersicht(behaelter) {
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
el.addEventListener("click", () => {
z.ordner = el.dataset.reiter;
rendern(behaelter);
});
});
const suchfeld = behaelter.querySelector("[data-suche]");
let warten = null;
suchfeld.addEventListener("input", () => {
clearTimeout(warten);
warten = setTimeout(() => {
z.suche = suchfeld.value.trim();
rendern(behaelter);
}, 280);
});
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", () => anlegenFenster(behaelter));
}
/* ---- Dokumentationsbogen ------------------------------------------------- */
function einzelAnsicht(behaelter, d) {
const b = d.behandlung, s = d.stamm;
const laufend = b.status === "laufend";
// Die Diagnose darf nur mit eigenem Recht geschrieben werden
const diagnoseSperre = !d.rechte.diagnose;
behaelter.innerHTML = `
${sicher(b.kennung)} · ${sicher(b.patientName)}
geb. ${sicher(b.geburt)}
${b.einsatzNummer ? " · Einsatz " + sicher(b.einsatzNummer) : ""}
${b.aufnahmeGrund ? " · Aufnahme: " + sicher(b.aufnahmeGrund) : ""}
${s.hinweis ? `
⚠
Warnhinweis — ${sicher(s.hinweis)}
` : ""}
${s.allergien ? `
💊
Allergien — ${sicher(s.allergien)}
Vor jeder Gabe selbst prüfen. Das System
gleicht Medikamente nicht gegen Allergien ab.
` : ""}
Status${sicher(b.status)}
Angelegt${zeit(b.erstelltAm)}
Von${sicher(b.erstelltVon)}
Blutgruppe
${sicher(s.blutgruppe || "nicht bestimmt")}
Vorerkrankungen
${sicher(s.vorerkrankungen || "keine erfasst")}
Dauermedikation
${sicher(s.dauermedikation || "keine erfasst")}
${b.abgeschlossenAm ? `
Abgeschlossen${zeit(b.abgeschlossenAm)}
Durch${sicher(b.abgeschlossenVon)}
` : ""}
${laufend && d.rechte.dokumentieren
? `
` : ""}
${laufend && d.rechte.abschliessen
? `` : ""}
${!laufend && d.rechte.abschliessen
? `` : ""}
${feld("symptome", "Symptome / Anamnese", b.symptome, laufend && d.rechte.dokumentieren,
"Was schildert der Patient?")}
${feld("untersuchung", "Untersuchung", b.untersuchung, laufend && d.rechte.dokumentieren,
"Was hast du erhoben? Vitalwerte, Befunde am Patienten…")}
${feld("diagnose", "Diagnose", b.diagnose,
laufend && d.rechte.dokumentieren && !diagnoseSperre,
diagnoseSperre ? "Für die Diagnose fehlt dir die Berechtigung."
: "Deine Einschätzung. Das System stellt keine Diagnose.")}
${feld("massnahmen", "Maßnahmen / Behandlung", b.massnahmen,
laufend && d.rechte.dokumentieren,
"Verbände, Lagerung, Eingriffe, Reanimation…")}
${feld("empfehlung", "Empfehlung", b.empfehlung, laufend && d.rechte.dokumentieren,
"Was empfiehlst du dem Patienten?")}
${feld("nachsorge", "Nachsorge", b.nachsorge, laufend && d.rechte.dokumentieren,
"Kontrolltermin, Wundversorgung, Medikamentenplan…")}
Verabreichte Medikamente
${laufend && d.rechte.medikament
? `` : ""}
${d.gaben.length ? d.gaben.map((g) => `
${g.btm ? "BTM" : "💊"}
${sicher(g.name)}
${g.menge} ${sicher(g.einheit)}
${g.dosierung ? sicher(g.dosierung) + " · " : ""}
${sicher(g.autor)} · ${zeit(g.gegeben_am)}
${g.hinweis ? " · " + sicher(g.hinweis) : ""}
${laufend && (g.eigene || d.rechte.bestand)
? `
` : ""}
`).join("")
: `
Keine Gabe dokumentiert.
`}
Laborergebnisse des Patienten
${d.labor.length ? d.labor.map((l) => `
${sicher(l.kennung)}
${sicher(l.art)}
${sicher(l.befund || l.ergebnis
|| "Noch kein Ergebnis")}
${sicher(l.status)}
`).join("")
: `
Keine Laboraufträge.
`}
Operationen des Patienten
${d.operationen.length ? d.operationen.map((o) => `
${sicher(o.kennung)}
${sicher(o.art)}
${o.beginn ? zeit(o.beginn) : "noch nicht begonnen"}
${sicher(o.status)}
`).join("")
: `
Keine Operationen.
`}
`;
verdrahteEinzel(behaelter, d);
}
/**
* Ein Feld des Dokumentationsbogens.
* @param {string} name Feldname
* @param {string} titel Beschriftung
* @param {string} wert Bisheriger Inhalt
* @param {boolean} schreibbar Darf bearbeitet werden?
* @param {string} hinweis Text unter dem Feld
*/
function feld(name, titel, wert, schreibbar, hinweis) {
if (!schreibbar) {
return `
${wert
? sicher(wert).replace(/\n/g, "
")
: "Nicht dokumentiert."}
${hinweis ? `
${sicher(hinweis)}
` : ""}
`;
}
return `
${hinweis ? `
${sicher(hinweis)}
` : ""}
`;
}
function verdrahteEinzel(behaelter, d) {
const b = d.behandlung;
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: b.patient });
});
// Baustein einsetzen: füllt nur den Bogen, gespeichert wird danach von Hand
const baustein = behaelter.querySelector("[data-baustein]");
if (baustein) baustein.addEventListener("click", () => {
Bausteine.einsetzen({ modul: "behandlung", behaelter });
});
const speichern = behaelter.querySelector("[data-speichern]");
if (speichern) speichern.addEventListener("click", async () => {
const lies = (name) => {
const el = behaelter.querySelector(`[name=${name}]`);
return el ? el.value.trim() : undefined;
};
try {
await aufruf("behandlung", "speichern", {
id: b.id,
symptome: lies("symptome"),
untersuchung: lies("untersuchung"),
diagnose: lies("diagnose"),
massnahmen: lies("massnahmen"),
empfehlung: lies("empfehlung"),
nachsorge: lies("nachsorge"),
});
meldung("Dokumentation gespeichert.", "gut");
oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
});
const abschliessen = behaelter.querySelector("[data-abschliessen]");
if (abschliessen) abschliessen.addEventListener("click", () => {
bestaetigen({
titel: "Behandlung abschließen",
inhalt: `
${sicher(b.kennung)} — ${sicher(b.patientName)}
Die Dokumentation wird damit schreibgeschützt. Sie lässt sich später
mit Begründung wieder öffnen.
${!b.diagnose ? `
📄
Es ist keine Diagnose eingetragen. Abschließen ist trotzdem
möglich — das System trägt keine für dich ein.
` : ""}
Nicht gespeicherte Änderungen im Bogen gehen dabei
verloren. Erst speichern, dann abschließen.
`,
bestaetigenText: "Abschließen",
bestaetigt: async (el, zu) => {
try {
await aufruf("behandlung", "abschliessen", {
id: b.id, vermerk: el.querySelector("[name=vermerk]").value.trim(),
});
zu();
meldung("Behandlung abgeschlossen.", "gut");
oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const wiederOeffnen = behaelter.querySelector("[data-oeffnen]");
if (wiederOeffnen) wiederOeffnen.addEventListener("click", () => {
bestaetigen({
titel: "Dokumentation wieder öffnen",
inhalt: `Die Behandlung wird wieder bearbeitbar. Der Vorgang steht
dauerhaft im Protokoll.
`,
bestaetigenText: "Wieder öffnen",
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("behandlung", "wiedereroeffnen", { id: b.id, grund });
zu();
meldung("Dokumentation wieder geöffnet.", "gut");
oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const gabe = behaelter.querySelector("[data-gabe]");
if (gabe) gabe.addEventListener("click", () => gabeFenster(behaelter, d));
behaelter.querySelectorAll("[data-zuruecknehmen]").forEach((el) => {
el.addEventListener("click", () => {
bestaetigen({
titel: "Eintrag zurücknehmen",
gefahr: true,
inhalt: `Der Eintrag wird aus der Dokumentation entfernt. Wurde damals
Bestand gebucht, wird er als Korrektur zurückgebucht.
`,
bestaetigenText: "Zurücknehmen",
bestaetigt: async (dialog, zu) => {
const grund = dialog.querySelector("[name=grund]").value.trim();
if (grund.length < 3) return meldung("Bitte eine Begründung angeben.", "fehler");
try {
await aufruf("behandlung", "medikamentZuruecknehmen", {
gabe: +el.dataset.zuruecknehmen, grund,
});
zu();
meldung("Eintrag zurückgenommen.", "gut");
oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
});
}
/* ---- Medikamentengabe ---------------------------------------------------- */
async function gabeFenster(behaelter, d) {
const b = d.behandlung;
let katalog;
try {
katalog = await aufruf("behandlung", "medikamente", {});
} catch (f) { return meldung(f.message, "fehler"); }
let gewaehlt = null;
// Wird gesetzt, wenn der Bestand nicht reicht und trotzdem dokumentiert wird
let ohneBestand = false;
// Medikamente nach Kategorie gruppieren
const gruppen = {};
katalog.medikamente.forEach((m) => {
(gruppen[m.kategorie] = gruppen[m.kategorie] || []).push(m);
});
const kategorieName = {};
katalog.kategorien.forEach((k) => { kategorieName[k.id] = k.name; });
const dialog = bestaetigen({
titel: "Medikamentengabe dokumentieren",
inhalt: `
${sicher(b.patientName)} · ${sicher(b.kennung)}
${d.stamm.allergien ? `
⚠
Bekannte Allergien: ${sicher(d.stamm.allergien)}
Das System prüft nicht gegen diese Liste.
Der Abgleich ist deine Aufgabe.
` : ""}
${Object.keys(gruppen).map((kategorie) => `
${sicher(kategorieName[kategorie] || kategorie)}
${gruppen[kategorie].map((m) => `
`).join("")}
`).join("")}
Die Standarddosierung ist ein Vorschlag aus dem
Katalog. Was du gibst, entscheidest du.
`,
bestaetigenText: "Gabe dokumentieren",
bestaetigt: async (el, zu) => {
if (!gewaehlt) return meldung("Bitte ein Medikament wählen.", "fehler");
try {
await aufruf("behandlung", "medikamentGeben", {
id: b.id,
medikament: gewaehlt,
menge: +el.querySelector("[name=menge]").value,
dosierung: el.querySelector("[name=dosierung]").value.trim(),
hinweis: el.querySelector("[name=hinweis]").value.trim(),
ohneBestand,
});
zu();
meldung("Gabe dokumentiert.", "gut");
oeffne(behaelter, b.id);
} catch (f) {
meldung(f.message, "fehler");
// Bestand reicht nicht: Dokumentation trotzdem anbieten
if (f.message.indexOf("Bestand") !== -1) zeigeBestandsWeg(el);
}
},
nachOeffnen: (hg) => {
const suchfeld = hg.querySelector("[name=suche]");
const dosierung = hg.querySelector("[name=dosierung]");
hg.querySelectorAll("[data-med]").forEach((el) => {
el.addEventListener("click", () => {
gewaehlt = +el.dataset.med;
ohneBestand = false;
hg.querySelector("[data-warnung]").style.display = "none";
hg.querySelectorAll("[data-med]").forEach((k) => k.classList.remove("gewaehlt"));
el.classList.add("gewaehlt");
// Standarddosierung als Vorschlag übernehmen, überschreibbar
if (!dosierung.value) dosierung.value = el.dataset.dosierung;
});
});
suchfeld.addEventListener("input", () => {
const begriff = suchfeld.value.trim().toLowerCase();
hg.querySelectorAll("[data-med]").forEach((el) => {
el.style.display = !begriff || el.dataset.name.indexOf(begriff) !== -1 ? "" : "none";
});
// Leere Gruppen ausblenden
hg.querySelectorAll("[data-gruppe]").forEach((gruppe) => {
const sichtbar = [...gruppe.querySelectorAll("[data-med]")]
.some((el) => el.style.display !== "none");
gruppe.style.display = sichtbar ? "" : "none";
});
});
},
});
/** Bietet an, die Gabe ohne Bestandsbuchung zu dokumentieren */
function zeigeBestandsWeg(el) {
const warnung = el.querySelector("[data-warnung]");
warnung.style.display = "";
warnung.innerHTML = `
📦
Der Lagerbestand reicht nicht. Eine Dokumentation darf daran
nicht scheitern — etwa wenn das Mittel aus dem RTW stammt.
`;
warnung.querySelector("[data-ohne]").addEventListener("click", () => {
ohneBestand = true;
meldung("Bestandsbuchung deaktiviert — jetzt bestätigen.", "gut");
warnung.querySelector("[data-ohne]").disabled = true;
});
}
return dialog;
}
/* ---- Anlegen ------------------------------------------------------------- */
function anlegenFenster(behaelter) {
let gewaehlt = null;
let einsaetze = [];
bestaetigen({
titel: "Behandlung dokumentieren",
inhalt: `
Mindestens zwei Zeichen eingeben.
Diagnose, Maßnahmen und Nachsorge trägst du
anschließend im Bogen nach.
`,
bestaetigenText: "Anlegen",
bestaetigt: async (el, zu) => {
if (!gewaehlt) return meldung("Bitte zuerst einen Patienten wählen.", "fehler");
const einsatzFeld = el.querySelector("[name=einsatz]");
try {
const antwort = await aufruf("behandlung", "anlegen", {
patient: gewaehlt.id,
aufnahme: gewaehlt.aufnahme || null,
einsatz: einsatzFeld && einsatzFeld.value ? +einsatzFeld.value : null,
symptome: el.querySelector("[name=symptome]").value.trim(),
untersuchung: el.querySelector("[name=untersuchung]").value.trim(),
});
zu();
meldung("Behandlung " + antwort.kennung + " angelegt.", "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]");
const einsatzfeld = hg.querySelector("[data-einsatzfeld]");
let warten = null;
suchfeld.addEventListener("input", () => {
clearTimeout(warten);
warten = setTimeout(async () => {
const begriff = suchfeld.value.trim();
if (begriff.length < 2) {
liste.innerHTML = `Mindestens zwei Zeichen eingeben.
`;
return;
}
try {
const antwort = await aufruf("behandlung", "patientSuche", { begriff });
const treffer = antwort.treffer || [];
einsaetze = antwort.einsaetze || [];
liste.innerHTML = treffer.length ? treffer.map((t) => `
${sicher(t.firstname)} ${sicher(t.lastname)}
geb. ${sicher(t.dob)}
${t.blutgruppe ? " · " + sicher(t.blutgruppe) : ""}
${t.hinweis ? `
⚠` : ""}
${t.allergien ? `
A` : ""}
${t.aufnahme ? `
aufgenommen` : ""}
`).join("")
: `Keine Person gefunden.
`;
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 = "";
// Laufende Einsätze anbieten, an denen man beteiligt ist
if (einsaetze.length) {
einsatzfeld.style.display = "";
hg.querySelector("[name=einsatz]").innerHTML =
`` +
einsaetze.map((e) =>
``
).join("");
}
});
});
} catch (f) { meldung(f.message, "fehler"); }
}, 260);
});
},
});
}
/* ---- Laden ---------------------------------------------------------------- */
async function oeffne(behaelter, behandlungId) {
try {
const d = await aufruf("behandlung", "oeffnen", { id: behandlungId });
z.offen = behandlungId;
z.offeneDaten = d;
einzelAnsicht(behaelter, d);
} catch (f) { meldung(f.message, "fehler"); }
}
async function rendern(behaelter) {
try {
z.daten = await aufruf("behandlung", "uebersicht", {
ordner: z.ordner, begriff: z.suche,
});
// Sprungziel aus einem Hinweis, z. B. „Dokumentation fehlt"
const ziel = App.zustand.modulZiel;
if (ziel && ziel.behandlung) {
App.zustand.modulZiel = null;
return oeffne(behaelter, ziel.behandlung);
}
if (z.offen) await oeffne(behaelter, z.offen);
else uebersicht(behaelter);
} catch (f) {
behaelter.innerHTML = `Behandlungen
Konnte nicht geladen werden: ${sicher(f.message)}
`;
}
}
App.registriereAnsicht("behandlung", { rendern });
App.registriereSuche(async (begriff) => {
try {
const d = await aufruf("behandlung", "uebersicht", {
ordner: "laufend", begriff,
});
return (d.liste || []).slice(0, 5).map((b) => ({
bereich: "Behandlung",
titel: b.kennung + " · " + b.patientName,
unter: b.diagnose || b.massnahmen || "Noch nichts eingetragen",
}));
} catch (e) { return []; }
});
})();