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

642 lines
26 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: Leitstelle und Einsatzverwaltung
Links die Einsätze, rechts die Kräfte. Eine Kraft lässt sich auf
einen Einsatz ziehen — das schickt eine ANFRAGE, keine Zuweisung.
Ob sie den Einsatz übernimmt, entscheidet sie selbst.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen, anClient } = App;
const z = {
ordner: "aktuell", // aktuell | archiv
daten: null,
offen: null,
};
const prioText = { 1: "Priorität 1", 2: "Priorität 2", 3: "Priorität 3", 4: "Priorität 4" };
/* ---- Leitstelle --------------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>Leitstelle</h2>
<p>Notrufe erzeugen offene Einsätze. Wer hinfährt, entscheidet die
Leitstelle — und die angefragte Kraft selbst.</p>
</div>
<div class="ls-kopf-aktionen">
${d.rechte.leitstelle && !d.istLeitstelle
? `<button class="knopf leise" data-uebernehmen="1">Leitstelle übernehmen</button>` : ""}
${d.rechte.erstellen
? `<button class="knopf" data-neu="1">+ Einsatz anlegen</button>` : ""}
</div>
</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">noch niemand gebunden</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Laufend</div>
<div class="kz-wert">${d.zaehler.laufend}</div>
<div class="kz-zusatz">Kräfte im Einsatz</div>
</div>
<div class="kennzahl">
<div class="kz-titel">Kräfte im Dienst</div>
<div class="kz-wert">${d.kraefte.length}</div>
<div class="kz-zusatz">${d.kraefte.filter((k) => !k.gebundenAn).length} ungebunden</div>
</div>
<div class="kennzahl ${d.leitstelleName ? "" : "warn"}">
<div class="kz-titel">Leitstelle</div>
<div class="kz-wert" style="font-size:16px">
${sicher(d.leitstelleName || "unbesetzt")}</div>
<div class="kz-zusatz">${d.zaehler.heute} Einsätze heute beendet</div>
</div>
</div>
<div class="reiter">
<button class="${z.ordner === "aktuell" ? "aktiv" : ""}" data-reiter="aktuell">
Aktuelle Einsätze</button>
<button class="${z.ordner === "archiv" ? "aktiv" : ""}" data-reiter="archiv">
Archiv</button>
</div>
<div class="ls-raster">
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">${z.ordner === "archiv"
? "Abgeschlossene Einsätze" : "Einsätze"}</span>
<span class="feld-hinweis">${d.rechte.zuweisen && z.ordner === "aktuell"
? "Kraft von rechts auf einen Einsatz ziehen"
: d.einsaetze.length + " Einträge"}</span>
</div>
<div class="ls-liste">
${d.einsaetze.length ? d.einsaetze.map(einsatzKarte).join("")
: `<div class="liste-leer">${z.ordner === "archiv"
? "Das Archiv ist leer." : "Keine offenen Einsätze."}</div>`}
</div>
</div>
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Kräfte im Dienst</span>
<span class="feld-hinweis">${d.rechte.zuweisen ? "ziehbar" : "Übersicht"}</span>
</div>
<div class="liste">
${d.kraefte.length ? d.kraefte.map((k) => kraftZeile(k, d)).join("")
: `<div class="liste-leer">Niemand im Dienst.</div>`}
</div>
</div>
</div>
`;
verdrahteUebersicht(behaelter);
}
function einsatzKarte(e) {
const abgeschlossen = e.status === "abgeschlossen";
return `<div class="ls-einsatz prio-${sicher(e.prioritaet)} ${e.ichDabei ? "meiner" : ""}"
data-einsatz="${e.id}" data-ziel="1">
<div class="ls-e-kopf">
<span class="ls-e-nummer">${sicher(e.nummer)}</span>
<span class="marker ${abgeschlossen ? "neutral"
: e.status === "offen" ? "warn" : "info"}">${sicher(e.status)}</span>
${e.ichDabei ? `<span class="marker gut">du bist dabei</span>` : ""}
<span class="db-zeit">${abgeschlossen
? zeit(e.geschlossen_am) : "offen seit " + minuten(e.offenSeit)}</span>
</div>
<div class="ls-e-titel">${sicher(e.titel)}</div>
<div class="ls-e-unter">
<span class="ls-e-kategorie" ${e.farbe ? `style="color:${sicher(e.farbe)}"` : ""}>
${sicher(e.kategorieName)}</span>
· ${sicher(prioText[e.prioritaet] || "")}
${e.ort ? " · " + sicher(e.ort) : ""}
${e.quelle === "notruf" ? ` · <span class="marker kritisch">Notruf</span>` : ""}
</div>
<div class="ls-e-fuss">
<span class="feld-hinweis">${e.kraefte} Kraft/Kräfte gebunden
· angelegt von ${sicher(e.erstelltVon)}</span>
<div class="ls-e-aktionen">
${e.pos_x ? `<button class="knopf leise klein" data-wegpunkt="${e.id}"
title="Wegpunkt setzen">◎</button>` : ""}
${!abgeschlossen ? (e.ichDabei
? `<button class="knopf leise klein" data-verlassen="${e.id}">Abrücken</button>`
: `<button class="knopf klein" data-beitreten="${e.id}">Übernehmen</button>`) : ""}
<button class="knopf leise klein" data-oeffnen="${e.id}">Details</button>
</div>
</div>
</div>`;
}
function kraftZeile(k, d) {
const farbe = (d.status.find((s) => s.id === k.status) || {}).farbe || "var(--flaeche-3)";
const funktion = (d.funktionen.find((f) => f.id === k.funktion) || {}).name || k.funktion;
return `<div class="liste-zeile ls-kraft" data-kraft="${k.quelle}"
${d.rechte.zuweisen && z.ordner === "aktuell" ? 'draggable="true"' : ""}>
<span class="db-dienstpunkt" style="background:${sicher(farbe)}"
title="${sicher(k.status)}"></span>
<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)} · ${sicher(funktion)}</div>
</div>
${k.leitstelle ? `<span class="marker info">Leitstelle</span>` : ""}
${k.gebundenAn
? `<span class="marker warn" title="Gebunden an einen Einsatz">
${sicher(k.gebundenAn)}</span>`
: `<span class="marker gut">frei</span>`}
</div>`;
}
function verdrahteUebersicht(behaelter) {
const d = z.daten;
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
el.addEventListener("click", () => {
z.ordner = el.dataset.reiter;
rendern(behaelter);
});
});
const neu = behaelter.querySelector("[data-neu]");
if (neu) neu.addEventListener("click", () => erstellenFenster(behaelter));
const uebernehmen = behaelter.querySelector("[data-uebernehmen]");
if (uebernehmen) uebernehmen.addEventListener("click", () => {
bestaetigen({
titel: "Leitstelle übernehmen",
inhalt: `<p>Du übernimmst die Disposition. Damit fragst du Kräfte für
Einsätze an — verpflichten kannst du niemanden.</p>`,
bestaetigenText: "Übernehmen",
bestaetigt: (_, zu) => {
zu();
anClient("leitstelle", {});
setTimeout(() => rendern(behaelter), 400);
},
});
});
behaelter.querySelectorAll("[data-oeffnen]").forEach((el) => {
el.addEventListener("click", (e) => {
e.stopPropagation();
oeffne(behaelter, +el.dataset.oeffnen);
});
});
behaelter.querySelectorAll("[data-beitreten]").forEach((el) => {
el.addEventListener("click", async (e) => {
e.stopPropagation();
try {
await aufruf("einsatz", "beitreten", { id: +el.dataset.beitreten });
meldung("Einsatz übernommen.", "gut");
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
});
});
behaelter.querySelectorAll("[data-verlassen]").forEach((el) => {
el.addEventListener("click", async (e) => {
e.stopPropagation();
try {
await aufruf("einsatz", "verlassen", { id: +el.dataset.verlassen });
meldung("Vom Einsatz abgerückt.", "gut");
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
});
});
behaelter.querySelectorAll("[data-wegpunkt]").forEach((el) => {
el.addEventListener("click", (e) => {
e.stopPropagation();
const einsatz = d.einsaetze.find((x) => x.id === +el.dataset.wegpunkt);
if (einsatz && einsatz.pos_x) {
anClient("wegpunkt", { x: einsatz.pos_x, y: einsatz.pos_y });
meldung("Wegpunkt gesetzt.");
}
});
});
if (d.rechte.zuweisen && z.ordner === "aktuell") verdrahteDisposition(behaelter);
}
/* ---- Disposition per Ziehen ---------------------------------------------- */
function verdrahteDisposition(behaelter) {
let gezogeneQuelle = null;
behaelter.querySelectorAll("[data-kraft]").forEach((el) => {
el.addEventListener("dragstart", (e) => {
gezogeneQuelle = +el.dataset.kraft;
el.classList.add("zieht");
// Ohne Nutzdaten lässt Firefox das Ziehen nicht zu
e.dataTransfer.setData("text/plain", String(gezogeneQuelle));
e.dataTransfer.effectAllowed = "link";
});
el.addEventListener("dragend", () => {
gezogeneQuelle = null;
el.classList.remove("zieht");
behaelter.querySelectorAll("[data-ziel]")
.forEach((ziel) => ziel.classList.remove("uebergabe"));
});
});
behaelter.querySelectorAll("[data-ziel]").forEach((el) => {
el.addEventListener("dragover", (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = "link";
el.classList.add("uebergabe");
});
el.addEventListener("dragleave", () => el.classList.remove("uebergabe"));
el.addEventListener("drop", (e) => {
e.preventDefault();
el.classList.remove("uebergabe");
const quelle = gezogeneQuelle || +e.dataTransfer.getData("text/plain");
if (quelle) anfrageFenster(behaelter, +el.dataset.einsatz, quelle);
});
});
}
function anfrageFenster(behaelter, einsatzId, zielQuelle) {
const einsatz = z.daten.einsaetze.find((e) => e.id === einsatzId);
const kraft = z.daten.kraefte.find((k) => k.quelle === zielQuelle);
if (!einsatz || !kraft) return;
bestaetigen({
titel: "Kraft anfragen",
inhalt: `
<p><b>${sicher(kraft.name)}</b> (${sicher(kraft.personalnummer)})
soll <b>${sicher(einsatz.nummer)}</b> übernehmen:</p>
<p>${sicher(einsatz.titel)}${einsatz.ort ? " — " + sicher(einsatz.ort) : ""}</p>
${kraft.gebundenAn ? `
<div class="warnung warn">
<span class="w-symbol">⚠</span>
<div>Diese Kraft ist bereits an <b>${sicher(kraft.gebundenAn)}</b> gebunden.</div>
</div>` : ""}
<div class="grundsatz">
<span>ℹ</span>
<div><b>Das ist eine Anfrage, keine Zuweisung.</b> Die Kraft bekommt den
Einsatz mit Wegpunkt angezeigt und entscheidet selbst, ob sie übernimmt.</div>
</div>`,
bestaetigenText: "Anfragen",
bestaetigt: async (_, zu) => {
try {
const antwort = await aufruf("einsatz", "anfragen", {
id: einsatzId, quelle: zielQuelle,
});
zu();
meldung("Anfrage an " + antwort.name + " gesendet.", "gut");
} catch (f) { meldung(f.message, "fehler"); }
},
});
}
/* ---- Einzelansicht --------------------------------------------------------- */
function einzelAnsicht(behaelter, d) {
const e = d.einsatz;
const aktiveKraefte = d.kraefte.filter((k) => k.aktiv);
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>${sicher(e.nummer)} · ${sicher(e.titel)}</h2>
<p>${sicher(e.kategorieName)} · ${sicher(prioText[e.prioritaet] || "")}
${e.ort ? " · " + sicher(e.ort) : ""}</p>
</div>
<button class="knopf leise" data-zurueck="1">← Leitstelle</button>
</div>
<div class="db-spalten">
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Einsatz</span>
<span class="marker ${e.status === "offen" ? "warn"
: e.status === "laufend" ? "info" : "neutral"}">${sicher(e.status)}</span>
</div>
<div class="werte-liste">
<div><span>Angelegt</span><b>${zeit(e.erstelltAm)}</b></div>
<div><span>Von</span><b>${sicher(e.erstelltVon)}</b></div>
<div><span>Quelle</span><b>${sicher(e.quelle)}</b></div>
${e.geschlossenAm ? `
<div><span>Abgeschlossen</span><b>${zeit(e.geschlossenAm)}</b></div>
<div><span>Durch</span><b>${sicher(e.geschlossenVon)}</b></div>` : ""}
</div>
${e.beschreibung
? `<div class="karte-fuss">${sicher(e.beschreibung).replace(/\n/g, "<br>")}</div>` : ""}
<div class="ls-e-aktionen" style="margin-top:14px">
${e.position ? `<button class="knopf leise klein" data-wegpunkt="1">
◎ Wegpunkt</button>` : ""}
${e.status !== "abgeschlossen" ? (d.ichDabei
? `<button class="knopf leise klein" data-verlassen="1">Abrücken</button>`
: `<button class="knopf klein" data-beitreten="1">Übernehmen</button>`) : ""}
${e.status !== "abgeschlossen" && d.rechte.zuweisen
? `<button class="knopf leise klein" data-prio="1">Priorität</button>` : ""}
${e.status !== "abgeschlossen" && d.rechte.schliessen
? `<button class="knopf gefahr klein" data-schliessen="1">Abschließen</button>` : ""}
</div>
</div>
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Beteiligte Kräfte</span>
<span class="feld-hinweis">${aktiveKraefte.length} gebunden</span>
</div>
<div class="liste">
${d.kraefte.length ? d.kraefte.map((k) => `
<div class="liste-zeile">
<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 || "")} · seit ${zeit(k.seit)}</div>
</div>
<span class="marker ${k.aktiv ? "gut" : "neutral"}">
${k.aktiv ? "im Einsatz" : "abgerückt"}</span>
</div>`).join("")
: `<div class="liste-leer">Noch niemand übernommen.</div>`}
</div>
</div>
</div>
<div class="karte" style="margin-top:14px">
<div class="karte-kopf">
<span class="karte-titel">Einsatzverlauf</span>
<button class="knopf klein" data-notiz="1">+ Notiz</button>
</div>
<div class="liste">
${d.notizen.length ? d.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)} · ${zeit(n.am)}</div>
</div>
</div>`).join("")
: `<div class="liste-leer">Noch keine Einträge im Verlauf.</div>`}
</div>
</div>
<div class="karte" style="margin-top:14px">
<div class="karte-kopf">
<span class="karte-titel">Dokumentation zu diesem Einsatz</span>
<span class="feld-hinweis">Das System legt keine an</span>
</div>
<div class="liste">
${d.behandlungen.length ? d.behandlungen.map((b) => `
<div class="liste-zeile">
<span class="db-nummer">${sicher(b.kennung)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(b.patientName)}</div>
<div class="lz-unter">${sicher(b.diagnose || "Noch keine Diagnose")}</div>
</div>
<span class="marker ${b.status === "laufend" ? "warn" : "gut"}">
${sicher(b.status)}</span>
</div>`).join("")
: `<div class="liste-leer">Noch keine Behandlung dokumentiert.</div>`}
</div>
</div>
`;
verdrahteEinzel(behaelter, d);
}
function verdrahteEinzel(behaelter, d) {
const e = d.einsatz;
behaelter.querySelector("[data-zurueck]").addEventListener("click", () => {
z.offen = null;
rendern(behaelter);
});
const wegpunkt = behaelter.querySelector("[data-wegpunkt]");
if (wegpunkt) wegpunkt.addEventListener("click", () => {
anClient("wegpunkt", { x: e.position.x, y: e.position.y });
meldung("Wegpunkt gesetzt.");
});
const beitreten = behaelter.querySelector("[data-beitreten]");
if (beitreten) beitreten.addEventListener("click", async () => {
try {
await aufruf("einsatz", "beitreten", { id: e.id });
meldung("Einsatz übernommen.", "gut");
oeffne(behaelter, e.id);
} catch (f) { meldung(f.message, "fehler"); }
});
const verlassen = behaelter.querySelector("[data-verlassen]");
if (verlassen) verlassen.addEventListener("click", async () => {
try {
await aufruf("einsatz", "verlassen", { id: e.id });
meldung("Abgerückt.", "gut");
oeffne(behaelter, e.id);
} catch (f) { meldung(f.message, "fehler"); }
});
behaelter.querySelector("[data-notiz]").addEventListener("click", () => {
fenster({
titel: "Notiz zum Einsatzverlauf",
inhalt: `<div class="feld"><label>Was ist passiert?</label>
<textarea name="text" rows="4"
placeholder="Lagemeldung, Beobachtung, Übergabe…"></textarea></div>`,
knoepfe: [
{ text: "Abbrechen", art: "leise", klick: (_, zu) => zu() },
{ text: "Speichern", klick: async (el, zu) => {
try {
await aufruf("einsatz", "notiz", {
id: e.id, text: el.querySelector("[name=text]").value.trim(),
});
zu();
oeffne(behaelter, e.id);
} catch (f) { meldung(f.message, "fehler"); }
} },
],
});
});
const prio = behaelter.querySelector("[data-prio]");
if (prio) prio.addEventListener("click", () => {
bestaetigen({
titel: "Priorität ändern",
inhalt: `
<p>Aktuell: <b>${sicher(prioText[e.prioritaet])}</b></p>
<div class="feld"><label>Neue Priorität</label>
<select name="prioritaet">
<option value="1" ${e.prioritaet === 1 ? "selected" : ""}>1 — Sofort</option>
<option value="2" ${e.prioritaet === 2 ? "selected" : ""}>2 — Dringend</option>
<option value="3" ${e.prioritaet === 3 ? "selected" : ""}>3 — Regulär</option>
<option value="4" ${e.prioritaet === 4 ? "selected" : ""}>4 — Nachrangig</option>
</select></div>
<div class="feld"><label>Begründung (Pflicht)</label>
<input name="grund" maxlength="255"></div>`,
bestaetigenText: "Setzen",
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("einsatz", "prioritaet", {
id: e.id,
prioritaet: +el.querySelector("[name=prioritaet]").value,
grund,
});
zu();
meldung("Priorität geändert.", "gut");
oeffne(behaelter, e.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const schliessen = behaelter.querySelector("[data-schliessen]");
if (schliessen) schliessen.addEventListener("click", () => {
const dokumentiert = d.behandlungen.length;
bestaetigen({
titel: "Einsatz abschließen",
gefahr: true,
inhalt: `
<p><b>${sicher(e.nummer)}</b> — ${sicher(e.titel)}</p>
<p>Alle noch gebundenen Kräfte werden abgemeldet.</p>
${dokumentiert === 0 ? `
<div class="warnung warn">
<span class="w-symbol">📄</span>
<div><b>Zu diesem Einsatz ist noch keine Behandlung dokumentiert.</b>
Das System legt keine an — du bekommst nur eine Erinnerung.
Abschließen ist trotzdem möglich.</div>
</div>` : ""}
<div class="feld"><label>Abschlussvermerk (Pflicht)</label>
<textarea name="vermerk" rows="4"
placeholder="Was wurde vorgefunden, was wurde getan, wie endete der Einsatz?"></textarea></div>`,
bestaetigenText: "Abschließen",
bestaetigt: async (el, zu) => {
const vermerk = el.querySelector("[name=vermerk]").value.trim();
if (vermerk.length < 5) return meldung("Bitte einen Vermerk angeben.", "fehler");
try {
await aufruf("einsatz", "schliessen", { id: e.id, vermerk });
zu();
meldung("Einsatz abgeschlossen.", "gut");
z.offen = null;
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
}
/* ---- Einsatz anlegen --------------------------------------------------------- */
function erstellenFenster(behaelter) {
const d = z.daten;
fenster({
titel: "Einsatz anlegen",
inhalt: `
<div class="feld"><label>Titel</label>
<input name="titel" maxlength="160" placeholder="Kurz und eindeutig"></div>
<div class="feld-reihe">
<div class="feld"><label>Kategorie</label>
<select name="kategorie">
${d.kategorien.map((k) =>
`<option value="${sicher(k.id)}">${sicher(k.name)}</option>`).join("")}
</select></div>
<div class="feld"><label>Priorität</label>
<select name="prioritaet">
<option value="1">1 — Sofort</option>
<option value="2">2 — Dringend</option>
<option value="3" selected>3 — Regulär</option>
<option value="4">4 — Nachrangig</option>
</select></div>
</div>
<div class="feld"><label>Ort</label>
<input name="ort" maxlength="160" placeholder="Straße, Gebäude, Bezirk…"></div>
<div class="feld"><label>Beschreibung</label>
<textarea name="beschreibung" rows="5"
placeholder="Was wurde gemeldet?"></textarea>
<div class="feld-hinweis">Der Einsatz erscheint für alle als <b>offen</b>.
Alarmiert wird niemand gezielt.</div></div>`,
knoepfe: [
{ text: "Abbrechen", art: "leise", klick: (_, zu) => zu() },
{ text: "Anlegen", klick: async (el, zu) => {
try {
await aufruf("einsatz", "erstellen", {
titel: el.querySelector("[name=titel]").value.trim(),
kategorie: el.querySelector("[name=kategorie]").value,
prioritaet: +el.querySelector("[name=prioritaet]").value,
ort: el.querySelector("[name=ort]").value.trim(),
beschreibung: el.querySelector("[name=beschreibung]").value.trim(),
});
zu();
meldung("Einsatz angelegt.", "gut");
z.ordner = "aktuell";
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
} },
],
});
}
/* ---- Laden --------------------------------------------------------------------- */
async function oeffne(behaelter, einsatzId) {
try {
const d = await aufruf("einsatz", "oeffnen", { id: einsatzId });
z.offen = einsatzId;
einzelAnsicht(behaelter, d);
} catch (f) { meldung(f.message, "fehler"); }
}
async function rendern(behaelter) {
try {
z.daten = await aufruf("einsatz", "uebersicht", { ordner: z.ordner });
// Sprungziel aus einem Hinweis, z. B. „Neuer Einsatz"
const ziel = App.zustand.modulZiel;
if (ziel && ziel.einsatz) {
App.zustand.modulZiel = null;
return oeffne(behaelter, ziel.einsatz);
}
if (z.offen) await oeffne(behaelter, z.offen);
else uebersicht(behaelter);
} catch (f) {
behaelter.innerHTML = `<div class="seiten-kopf"><div><h2>Leitstelle</h2>
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
}
}
App.registriereAnsicht("einsatz", {
rendern,
aktualisieren() {
if (App.zustand.aktivesModul === "einsatz") rendern(App.$("inhalt"));
},
});
App.registriereSuche(async (begriff) => {
try {
const d = await aufruf("einsatz", "uebersicht", { ordner: "aktuell" });
const klein = begriff.toLowerCase();
return (d.einsaetze || [])
.filter((e) => (e.nummer + " " + e.titel + " " + (e.ort || ""))
.toLowerCase().includes(klein))
.slice(0, 5)
.map((e) => ({
bereich: "Einsatz", titel: e.nummer + " · " + e.titel,
unter: e.kategorieName + (e.ort ? " · " + e.ort : ""),
}));
} catch (e) { return []; }
});
})();