- 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>
604 lines
24 KiB
JavaScript
604 lines
24 KiB
JavaScript
/* =====================================================================
|
||
aether-medical / Ansicht: Labor
|
||
|
||
Zwei Blöcke, streng getrennt: das ERGEBNIS trägt das Labor ein, den
|
||
BEFUND ein Mediziner. Dazwischen liegt die Bewertung — und die nimmt
|
||
das System niemandem ab. Kein Wert wird markiert, verglichen oder
|
||
als auffällig dargestellt.
|
||
===================================================================== */
|
||
"use strict";
|
||
|
||
(() => {
|
||
const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen } = App;
|
||
|
||
const z = {
|
||
ordner: "offen", // offen | befund | meine | archiv
|
||
art: "",
|
||
suche: "",
|
||
daten: null,
|
||
offen: null,
|
||
};
|
||
|
||
const statusArt = {
|
||
offen: "warn", in_arbeit: "info", fertig: "gut", verworfen: "neutral",
|
||
};
|
||
const statusName = {
|
||
offen: "offen", in_arbeit: "in Arbeit", fertig: "Ergebnis liegt vor",
|
||
verworfen: "verworfen",
|
||
};
|
||
|
||
/* ---- Übersicht ---------------------------------------------------------- */
|
||
|
||
function uebersicht(behaelter) {
|
||
const d = z.daten;
|
||
|
||
behaelter.innerHTML = `
|
||
<div class="seiten-kopf">
|
||
<div>
|
||
<h2>Labor</h2>
|
||
<p>Das Labor speichert Ergebnisse. Ob ein Wert etwas bedeutet,
|
||
entscheidet ein Mediziner — nicht das System.</p>
|
||
</div>
|
||
${d.rechte.beauftragen
|
||
? `<button class="knopf" data-neu="1">+ Laborauftrag stellen</button>` : ""}
|
||
</div>
|
||
|
||
<div class="raster k4">
|
||
<div class="kennzahl ${d.zaehler.offen > 0 ? "warn" : ""}">
|
||
<div class="kz-titel">Offen</div>
|
||
<div class="kz-wert">${d.zaehler.offen}</div>
|
||
<div class="kz-zusatz">wartet auf das Labor</div>
|
||
</div>
|
||
<div class="kennzahl ${d.zaehler.dringend > 0 ? "hoch" : ""}">
|
||
<div class="kz-titel">Davon dringend</div>
|
||
<div class="kz-wert">${d.zaehler.dringend}</div>
|
||
<div class="kz-zusatz">vorrangig bearbeiten</div>
|
||
</div>
|
||
<div class="kennzahl ${d.zaehler.befund > 0 ? "warn" : ""}">
|
||
<div class="kz-titel">Ohne Bewertung</div>
|
||
<div class="kz-wert">${d.zaehler.befund}</div>
|
||
<div class="kz-zusatz">Ergebnis da, Befund fehlt</div>
|
||
</div>
|
||
<div class="kennzahl">
|
||
<div class="kz-titel">Eigene Aufträge</div>
|
||
<div class="kz-wert">${d.zaehler.meine}</div>
|
||
<div class="kz-zusatz">von dir gestellt</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="reiter">
|
||
<button class="${z.ordner === "offen" ? "aktiv" : ""}" data-reiter="offen">
|
||
Im Labor<span class="mdt-zahl">${d.zaehler.offen}</span></button>
|
||
<button class="${z.ordner === "befund" ? "aktiv" : ""}" data-reiter="befund">
|
||
Bewertung offen<span class="mdt-zahl">${d.zaehler.befund}</span></button>
|
||
<button class="${z.ordner === "meine" ? "aktiv" : ""}" data-reiter="meine">
|
||
Meine Aufträge</button>
|
||
<button class="${z.ordner === "archiv" ? "aktiv" : ""}" data-reiter="archiv">
|
||
Archiv</button>
|
||
</div>
|
||
|
||
<div class="filterleiste">
|
||
<input data-suche="1" placeholder="Kennung oder Patient…" value="${sicher(z.suche)}">
|
||
<select data-art="1">
|
||
<option value="">Alle Probenarten</option>
|
||
${d.arten.map((a) =>
|
||
`<option value="${sicher(a.id)}" ${a.id === z.art ? "selected" : ""}>
|
||
${sicher(a.name)}</option>`).join("")}
|
||
</select>
|
||
</div>
|
||
|
||
<div class="karte">
|
||
<div class="karte-kopf">
|
||
<span class="karte-titel">Aufträge</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">Kein Auftrag gefunden.</div>`}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
verdrahteUebersicht(behaelter);
|
||
}
|
||
|
||
function zeile(l) {
|
||
return `<div class="liste-zeile" data-oeffne="${l.id}">
|
||
${l.dringend ? `<span class="prio p1" title="Dringend"></span>`
|
||
: `<span class="prio p4"></span>`}
|
||
<span class="db-nummer">${sicher(l.kennung)}</span>
|
||
<div class="lz-haupt">
|
||
<div class="lz-titel">${sicher(l.patientName)}
|
||
<span class="pat-geburt">${sicher(l.artName)}</span></div>
|
||
<div class="lz-unter">${sicher(String(l.fragestellung || "").slice(0, 90))}</div>
|
||
</div>
|
||
|
||
${l.dringend ? `<span class="marker kritisch">dringend</span>` : ""}
|
||
${l.hatErgebnis && !l.hatBefund
|
||
? `<span class="marker warn">Befund fehlt</span>` : ""}
|
||
${l.hatBefund ? `<span class="marker gut">befundet</span>` : ""}
|
||
${l.eigene ? `<span class="marker neutral">dein Auftrag</span>` : ""}
|
||
|
||
<span class="marker ${statusArt[l.status] || "neutral"}">
|
||
${sicher(statusName[l.status] || l.status)}</span>
|
||
<span class="db-zeit">${l.status === "offen" || l.status === "in_arbeit"
|
||
? "seit " + minuten(l.offenSeit) : zeit(l.auftrag_am)}</span>
|
||
</div>`;
|
||
}
|
||
|
||
function verdrahteUebersicht(behaelter) {
|
||
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
|
||
el.addEventListener("click", () => {
|
||
z.ordner = el.dataset.reiter;
|
||
rendern(behaelter);
|
||
});
|
||
});
|
||
|
||
behaelter.querySelector("[data-art]").addEventListener("change", (e) => {
|
||
z.art = e.target.value;
|
||
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", () => auftragFenster(behaelter));
|
||
}
|
||
|
||
/* ---- Einzelansicht -------------------------------------------------------- */
|
||
|
||
function einzelAnsicht(behaelter, d) {
|
||
const a = d.auftrag, s = d.stamm;
|
||
const offen = a.status === "offen";
|
||
const inArbeit = a.status === "in_arbeit";
|
||
const verworfen = a.status === "verworfen";
|
||
|
||
behaelter.innerHTML = `
|
||
<div class="seiten-kopf">
|
||
<div>
|
||
<h2>${sicher(a.kennung)} · ${sicher(a.artName)}</h2>
|
||
<p>${sicher(a.patientName)} · geb. ${sicher(a.geburt)}
|
||
${a.dringend ? " · <b>dringend</b>" : ""}</p>
|
||
</div>
|
||
<button class="knopf leise" data-zurueck="1">← Übersicht</button>
|
||
</div>
|
||
|
||
${s.hinweis ? `
|
||
<div class="warnung kritisch">
|
||
<span class="w-symbol">⚠</span>
|
||
<div><b>Warnhinweis</b> — ${sicher(s.hinweis)}</div>
|
||
</div>` : ""}
|
||
|
||
<div class="beh-kopf">
|
||
<div class="werte-liste">
|
||
<div><span>Status</span><b>${sicher(statusName[a.status] || a.status)}</b></div>
|
||
<div><span>Beauftragt</span><b>${zeit(a.auftragAm)}</b></div>
|
||
<div><span>Von</span><b>${sicher(a.auftragVon)}</b></div>
|
||
${a.bearbeitetVon ? `
|
||
<div><span>Im Labor</span><b>${sicher(a.bearbeitetVon)}</b></div>
|
||
<div><span>Angenommen</span><b>${zeit(a.bearbeitetAm)}</b></div>` : ""}
|
||
<div class="${s.blutgruppe ? "" : "leer"}"><span>Blutgruppe (Stammdaten)</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>
|
||
${offen && d.rechte.bearbeiten
|
||
? `<button class="knopf klein" data-annehmen="1">In Arbeit nehmen</button>` : ""}
|
||
${(offen || inArbeit) && d.rechte.bearbeiten
|
||
? `<button class="knopf leise klein" data-verwerfen="1">Probe verwerfen</button>` : ""}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="feld" style="margin-bottom:16px">
|
||
<label>Fragestellung des Auftraggebers</label>
|
||
<div class="beh-text">${sicher(a.fragestellung || "—")}</div>
|
||
</div>
|
||
|
||
<div class="lab-spalten">
|
||
<!-- Block 1: die gemessenen Werte -->
|
||
<div class="karte lab-block">
|
||
<div class="karte-kopf">
|
||
<span class="karte-titel">Ergebnis — gemessene Werte</span>
|
||
${d.rechte.bearbeiten && !verworfen
|
||
? `<button class="knopf klein" data-ergebnis="1">
|
||
${a.ergebnis ? "Ergebnis ändern" : "Ergebnis eintragen"}</button>` : ""}
|
||
</div>
|
||
<div class="beh-text ${a.ergebnis ? "" : "leer"}">${a.ergebnis
|
||
? sicher(a.ergebnis).replace(/\n/g, "<br>")
|
||
: "Das Labor hat noch nichts eingetragen."}</div>
|
||
<div class="karte-fuss">
|
||
Reine Messwerte. Das System vergleicht sie mit nichts und markiert
|
||
nichts als auffällig.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Block 2: die ärztliche Bewertung -->
|
||
<div class="karte lab-block ${a.ergebnis && !a.befund ? "offen" : ""}">
|
||
<div class="karte-kopf">
|
||
<span class="karte-titel">Befund — ärztliche Bewertung</span>
|
||
${d.rechte.befunden && a.ergebnis && !verworfen
|
||
? `<button class="knopf klein" data-befund="1">
|
||
${a.befund ? "Befund ändern" : "Befund eintragen"}</button>` : ""}
|
||
</div>
|
||
<div class="beh-text ${a.befund ? "" : "leer"}">${a.befund
|
||
? sicher(a.befund).replace(/\n/g, "<br>")
|
||
: a.ergebnis
|
||
? "Das Ergebnis ist noch nicht bewertet."
|
||
: "Erst wenn ein Ergebnis vorliegt, lässt sich befunden."}</div>
|
||
<div class="karte-fuss">
|
||
${a.befundVon
|
||
? `Bewertet von <b>${sicher(a.befundVon)}</b> am ${zeit(a.befundAm)}.`
|
||
: "Die Bewertung stammt immer von einem Mediziner."}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
${a.ergebnis && !verworfen && d.rechte.stammdaten ? `
|
||
<div class="karte" style="margin-top:14px">
|
||
<div class="karte-kopf">
|
||
<span class="karte-titel">Blutgruppe in die Stammdaten übernehmen</span>
|
||
</div>
|
||
<p class="feld-hinweis" style="margin-top:0">
|
||
Falls in diesem Auftrag eine Blutgruppe bestimmt wurde: sie wandert
|
||
<b>nicht von selbst</b> in die Patientenakte. Du trägst sie ausdrücklich
|
||
ein und stehst dafür gerade.
|
||
</p>
|
||
<button class="knopf leise klein" data-blutgruppe="1" style="margin-top:10px">
|
||
Blutgruppe eintragen${s.blutgruppe ? " (aktuell: " + sicher(s.blutgruppe) + ")" : ""}
|
||
</button>
|
||
</div>` : ""}
|
||
|
||
<div class="karte" style="margin-top:14px">
|
||
<div class="karte-kopf">
|
||
<span class="karte-titel">Frühere Aufträge dieses Patienten</span>
|
||
<span class="feld-hinweis">zum Vergleich — nicht verrechnet</span>
|
||
</div>
|
||
<div class="liste">
|
||
${d.frueher.length ? d.frueher.map((f) => `
|
||
<div class="liste-zeile klickbar" data-frueher="${f.id}">
|
||
<span class="db-nummer">${sicher(f.kennung)}</span>
|
||
<div class="lz-haupt">
|
||
<div class="lz-titel">${sicher(f.art)}</div>
|
||
<div class="lz-unter">${zeit(f.auftrag_am)}</div>
|
||
</div>
|
||
<span class="marker ${statusArt[f.status] || "neutral"}">
|
||
${sicher(statusName[f.status] || f.status)}</span>
|
||
</div>`).join("")
|
||
: `<div class="liste-leer">Keine früheren Aufträge.</div>`}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
verdrahteEinzel(behaelter, d);
|
||
}
|
||
|
||
function verdrahteEinzel(behaelter, d) {
|
||
const a = d.auftrag;
|
||
|
||
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 });
|
||
});
|
||
|
||
behaelter.querySelectorAll("[data-frueher]").forEach((el) => {
|
||
el.addEventListener("click", () => oeffne(behaelter, +el.dataset.frueher));
|
||
});
|
||
|
||
const annehmen = behaelter.querySelector("[data-annehmen]");
|
||
if (annehmen) annehmen.addEventListener("click", async () => {
|
||
try {
|
||
await aufruf("labor", "annehmen", { id: a.id });
|
||
meldung("Auftrag in Arbeit genommen.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
});
|
||
|
||
const ergebnis = behaelter.querySelector("[data-ergebnis]");
|
||
if (ergebnis) ergebnis.addEventListener("click", () => ergebnisFenster(behaelter, d));
|
||
|
||
const befund = behaelter.querySelector("[data-befund]");
|
||
if (befund) befund.addEventListener("click", () => befundFenster(behaelter, d));
|
||
|
||
const verwerfen = behaelter.querySelector("[data-verwerfen]");
|
||
if (verwerfen) verwerfen.addEventListener("click", () => {
|
||
bestaetigen({
|
||
titel: "Probe verwerfen",
|
||
gefahr: true,
|
||
inhalt: `<p><b>${sicher(a.kennung)}</b> wird als verworfen abgelegt.
|
||
Der Auftraggeber wird informiert; ein neuer Auftrag muss von Hand
|
||
gestellt werden.</p>
|
||
<div class="feld"><label>Begründung (Pflicht)</label>
|
||
<textarea name="grund" rows="3"
|
||
placeholder="z. B. Probe hämolytisch, Röhrchen unbeschriftet"></textarea></div>`,
|
||
bestaetigenText: "Verwerfen",
|
||
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("labor", "verwerfen", { id: a.id, grund });
|
||
zu();
|
||
meldung("Auftrag verworfen.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
},
|
||
});
|
||
});
|
||
|
||
const blutgruppe = behaelter.querySelector("[data-blutgruppe]");
|
||
if (blutgruppe) blutgruppe.addEventListener("click", () => blutgruppeFenster(behaelter, d));
|
||
}
|
||
|
||
/* ---- Ergebnis und Befund --------------------------------------------------- */
|
||
|
||
function ergebnisFenster(behaelter, d) {
|
||
const a = d.auftrag;
|
||
|
||
fenster({
|
||
titel: "Ergebnis eintragen · " + a.kennung,
|
||
inhalt: `
|
||
<p class="feld-hinweis" style="margin-top:0">
|
||
<b>${sicher(a.patientName)}</b> · ${sicher(a.artName)}<br>
|
||
Fragestellung: ${sicher(a.fragestellung || "—")}
|
||
</p>
|
||
<div class="feld"><label>Gemessene Werte</label>
|
||
<textarea name="ergebnis" rows="14"
|
||
placeholder="Werte des Labors…">${sicher(a.ergebnis || "")}</textarea>
|
||
<div class="feld-hinweis">Nur Messwerte, keine Bewertung. Die Einordnung
|
||
nimmt anschließend ein Mediziner im Befund vor.</div></div>
|
||
${d.vorlage && !a.ergebnis
|
||
? `<button class="knopf leise klein" data-vorlage="1">Vorlage einfügen</button>` : ""}`,
|
||
knoepfe: [
|
||
{ text: "Abbrechen", art: "leise", klick: (_, zu) => zu() },
|
||
{ text: "Ergebnis speichern", klick: async (el, zu) => {
|
||
try {
|
||
await aufruf("labor", "ergebnis", {
|
||
id: a.id, ergebnis: el.querySelector("[name=ergebnis]").value.trim(),
|
||
});
|
||
zu();
|
||
meldung("Ergebnis gespeichert. Der Auftraggeber wurde informiert.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
} },
|
||
],
|
||
nachOeffnen: (hg) => {
|
||
const vorlage = hg.querySelector("[data-vorlage]");
|
||
if (vorlage) vorlage.addEventListener("click", () => {
|
||
const feld = hg.querySelector("[name=ergebnis]");
|
||
feld.value = d.vorlage;
|
||
feld.focus();
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
function befundFenster(behaelter, d) {
|
||
const a = d.auftrag;
|
||
|
||
bestaetigen({
|
||
titel: "Befund eintragen · " + a.kennung,
|
||
inhalt: `
|
||
<div class="feld"><label>Ergebnis des Labors</label>
|
||
<div class="beh-text">${sicher(a.ergebnis).replace(/\n/g, "<br>")}</div></div>
|
||
<div class="feld"><label>Deine Bewertung</label>
|
||
<textarea name="befund" rows="7"
|
||
placeholder="Wie ordnest du die Werte ein?">${sicher(a.befund || "")}</textarea>
|
||
<div class="feld-hinweis">Der Befund erscheint in der Patientenakte und im
|
||
Behandlungsbogen. Er trägt deinen Namen.</div></div>`,
|
||
bestaetigenText: "Befund speichern",
|
||
bestaetigt: async (el, zu) => {
|
||
try {
|
||
await aufruf("labor", "befund", {
|
||
id: a.id, befund: el.querySelector("[name=befund]").value.trim(),
|
||
});
|
||
zu();
|
||
meldung("Befund gespeichert.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Blutgruppe übernehmen -------------------------------------------------- */
|
||
|
||
function blutgruppeFenster(behaelter, d) {
|
||
const a = d.auftrag;
|
||
|
||
bestaetigen({
|
||
titel: "Blutgruppe in die Stammdaten übernehmen",
|
||
inhalt: `
|
||
<div class="feld"><label>Ergebnis des Labors</label>
|
||
<div class="beh-text">${sicher(a.ergebnis).replace(/\n/g, "<br>")}</div></div>
|
||
<div class="feld"><label>Blutgruppe für ${sicher(a.patientName)}</label>
|
||
<select name="blutgruppe">
|
||
<option value="">— bitte wählen —</option>
|
||
${d.blutgruppen.map((b) =>
|
||
`<option value="${sicher(b)}" ${b === d.stamm.blutgruppe ? "selected" : ""}>
|
||
${sicher(b)}</option>`).join("")}
|
||
</select>
|
||
<div class="feld-hinweis">Das System liest die Blutgruppe <b>nicht</b> aus dem
|
||
Ergebnistext aus. Du wählst sie, und damit steht dein Name darunter.</div></div>
|
||
${d.stamm.blutgruppe ? `
|
||
<div class="warnung warn">
|
||
<span class="w-symbol">⚠</span>
|
||
<div>In den Stammdaten steht bereits <b>${sicher(d.stamm.blutgruppe)}</b>.
|
||
Ein Überschreiben wird protokolliert.</div>
|
||
</div>` : ""}`,
|
||
bestaetigenText: "Übernehmen",
|
||
bestaetigt: async (el, zu) => {
|
||
const gewaehlt = el.querySelector("[name=blutgruppe]").value;
|
||
if (!gewaehlt) return meldung("Bitte eine Blutgruppe wählen.", "fehler");
|
||
try {
|
||
const antwort = await aufruf("labor", "blutgruppeUebernehmen", {
|
||
id: a.id, blutgruppe: gewaehlt,
|
||
});
|
||
zu();
|
||
meldung("Blutgruppe " + antwort.blutgruppe + " eingetragen.", "gut");
|
||
oeffne(behaelter, a.id);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Auftrag stellen --------------------------------------------------------- */
|
||
|
||
function auftragFenster(behaelter) {
|
||
const d = z.daten;
|
||
let gewaehlt = null;
|
||
|
||
bestaetigen({
|
||
titel: "Laborauftrag stellen",
|
||
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>Probenart</label>
|
||
<select name="art">
|
||
${d.arten.map((a) =>
|
||
`<option value="${sicher(a.id)}">${sicher(a.name)}</option>`).join("")}
|
||
</select></div>
|
||
<div class="feld"><label>Fragestellung</label>
|
||
<textarea name="fragestellung" rows="3"
|
||
placeholder="Wonach soll gesucht werden?"></textarea>
|
||
<div class="feld-hinweis">Je genauer die Frage, desto brauchbarer das
|
||
Ergebnis. Das Labor misst — bewerten musst du selbst.</div></div>
|
||
<div class="feld">
|
||
<label><input type="checkbox" name="dringend" class="haken"> Dringend</label>
|
||
<div class="feld-hinweis">Dringende Aufträge stehen im Labor oben und
|
||
erzeugen einen Hinweis mit hoher Priorität. Bearbeitet werden sie
|
||
trotzdem von einem Menschen.</div>
|
||
</div>
|
||
</div>`,
|
||
bestaetigenText: "Auftrag stellen",
|
||
bestaetigt: async (el, zu) => {
|
||
if (!gewaehlt) return meldung("Bitte zuerst einen Patienten wählen.", "fehler");
|
||
try {
|
||
const antwort = await aufruf("labor", "beauftragen", {
|
||
patient: gewaehlt.id,
|
||
art: el.querySelector("[name=art]").value,
|
||
fragestellung: el.querySelector("[name=fragestellung]").value.trim(),
|
||
dringend: el.querySelector("[name=dringend]").checked,
|
||
});
|
||
zu();
|
||
meldung("Auftrag " + antwort.kennung + " gestellt.", "gut");
|
||
z.ordner = "meine";
|
||
rendern(behaelter);
|
||
} 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("labor", "patientSuche", { begriff });
|
||
const treffer = antwort.treffer || [];
|
||
|
||
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)
|
||
: " · Blutgruppe nicht bestimmt"}</div>
|
||
</div>
|
||
${t.hinweis ? `<span class="marker kritisch">⚠</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 = "";
|
||
});
|
||
});
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
}, 260);
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Laden -------------------------------------------------------------------- */
|
||
|
||
async function oeffne(behaelter, laborId) {
|
||
try {
|
||
const d = await aufruf("labor", "oeffnen", { id: laborId });
|
||
z.offen = laborId;
|
||
einzelAnsicht(behaelter, d);
|
||
} catch (f) { meldung(f.message, "fehler"); }
|
||
}
|
||
|
||
async function rendern(behaelter) {
|
||
try {
|
||
z.daten = await aufruf("labor", "uebersicht", {
|
||
ordner: z.ordner, art: z.art, begriff: z.suche,
|
||
});
|
||
|
||
// Sprungziel aus einem Hinweis, z. B. „Laborergebnis verfügbar"
|
||
const ziel = App.zustand.modulZiel;
|
||
if (ziel && ziel.labor) {
|
||
App.zustand.modulZiel = null;
|
||
return oeffne(behaelter, ziel.labor);
|
||
}
|
||
|
||
if (z.offen) await oeffne(behaelter, z.offen);
|
||
else uebersicht(behaelter);
|
||
} catch (f) {
|
||
behaelter.innerHTML = `<div class="seiten-kopf"><div><h2>Labor</h2>
|
||
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
|
||
}
|
||
}
|
||
|
||
App.registriereAnsicht("labor", {
|
||
rendern,
|
||
aktualisieren() {
|
||
if (!z.offen && App.zustand.aktivesModul === "labor") rendern(App.$("inhalt"));
|
||
},
|
||
});
|
||
|
||
App.registriereSuche(async (begriff) => {
|
||
try {
|
||
const d = await aufruf("labor", "uebersicht", { ordner: "archiv", begriff });
|
||
return (d.liste || []).slice(0, 5).map((l) => ({
|
||
bereich: "Labor",
|
||
titel: l.kennung + " · " + l.patientName,
|
||
unter: l.artName + " · " + (statusName[l.status] || l.status),
|
||
}));
|
||
} catch (e) { return []; }
|
||
});
|
||
})();
|