/* =====================================================================
aether-police / Ansicht: Einsätze & Dispatch
Zwei Spalten: links das Einsatzaufkommen, rechts die verfügbaren
Einheiten. Zugewiesen wird nur auf ausdrückliche Anweisung der
Leitstelle — und auch dann entscheidet die Einheit selbst, ob sie
beitritt.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, aufruf, meldung, fenster, bestaetigen, $, anClient } = App;
const z = {
daten: null,
offenerEinsatz: null,
nurOffene: true,
};
/* ---- Bausteine --------------------------------------------------- */
const statusText = {
offen: "Offen", zugewiesen: "Zugewiesen",
vorort: "Vor Ort", abgeschlossen: "Abgeschlossen",
};
const statusArt = {
offen: "kritisch", zugewiesen: "warn", vorort: "info", abgeschlossen: "neutral",
};
const quelleText = {
manuell: "manuell erfasst", notruf: "Notruf", panik: "Panikknopf", extern: "extern",
};
/** Farbe einer Kategorie aus der Serverkonfiguration */
function kategorieFarbe(id) {
const treffer = (z.daten.kategorien || []).find((k) => k.id === id);
return treffer ? treffer.farbe : "#8a93a5";
}
function kategorieName(id) {
const treffer = (z.daten.kategorien || []).find((k) => k.id === id);
return treffer ? treffer.name : id;
}
function einsatzKarte(e) {
return `
${sicher(e.titel)}
${e.ichDabei > 0 ? 'Du bist dabei ' : ""}
${sicher(kategorieName(e.kategorie))}
${sicher(e.nummer)} · ${sicher(e.ort || "Ort unbekannt")} · ${zeit(e.erstellt_am)}
${e.quelle !== "manuell" ? " · " + sicher(quelleText[e.quelle] || e.quelle) : ""}
${sicher(statusText[e.status] || e.status)}
${e.einheiten || 0} 🚓
`;
}
function einheitenListe(beamte) {
if (!beamte.length) return `Niemand im Dienst.
`;
const farbe = { verfuegbar: "gut", einsatz: "warn", pause: "neutral", nicht: "kritisch" };
const text = { verfuegbar: "Verfügbar", einsatz: "Im Einsatz", pause: "Pause", nicht: "Nicht verfügbar" };
return `${beamte.map((b) =>
`
${sicher(b.dienstnummer)}
${sicher(b.name)}
${b.leitstelle ? 'Leitstelle ' : ""}
${sicher(text[b.status] || b.status)} · ${sicher(b.abteilung)}
`).join("")}
`;
}
/* ---- Übersicht --------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
const offene = d.einsaetze.filter((e) => e.status === "offen");
const laufende = d.einsaetze.filter((e) => e.status !== "offen" && e.status !== "abgeschlossen");
const erledigt = d.einsaetze.filter((e) => e.status === "abgeschlossen");
behaelter.innerHTML = `
Einsätze & Dispatch
${d.istLeitstelle
? "Du führst die Leitstelle — du entscheidest über die Zuweisung."
: "Einsätze werden nicht automatisch verteilt. Tritt bei, wenn du übernimmst."}
${z.nurOffene ? "Auch abgeschlossene" : "Nur laufende"}
+ Einsatz anlegen
Offen — keine Einheit zugeordnet
${offene.length}
${offene.length ? offene.map(einsatzKarte).join("")
: `
Keine offenen Einsätze.
`}
In Bearbeitung
${laufende.length}
${laufende.length ? laufende.map(einsatzKarte).join("")
: `
Keine laufenden Einsätze.
`}
${!z.nurOffene ? `
Abgeschlossen
${erledigt.length}
${erledigt.length ? erledigt.map(einsatzKarte).join("")
: `
Nichts abgeschlossen.
`}
` : ""}
Einheiten im Dienst
${d.beamteImDienst.length}
${einheitenListe(d.beamteImDienst)}
ℹ
Eine Zuweisung ist eine Aufforderung , kein Befehl an das System.
Die Einheit entscheidet selbst, ob sie den Einsatz übernimmt.
`;
$("e-neu").addEventListener("click", () => einsatzAnlegen(behaelter));
$("e-filter").addEventListener("click", () => {
z.nurOffene = !z.nurOffene;
rendern(behaelter);
});
behaelter.querySelectorAll("[data-einsatz]").forEach((el) =>
el.addEventListener("click", () => oeffneEinsatz(behaelter, +el.dataset.einsatz)));
}
/* ---- Einzelansicht ------------------------------------------------ */
function einsatzAnsicht(behaelter, d) {
const e = d.einsatz;
const aktiv = e.status !== "abgeschlossen";
const beteiligt = (e.beamte || []).filter((b) => !b.verlassen);
behaelter.innerHTML = `
${sicher(e.titel)}
${sicher(e.nummer)} · ${sicher(kategorieName(e.kategorie))} ·
${sicher(quelleText[e.quelle] || e.quelle)} · ${zeit(e.erstellt_am)}
‹ Zurück
Lage
${sicher(statusText[e.status] || e.status)}
Priorität ${sicher(e.prioritaet)} ${e.prioritaet === 1 ? "(höchste)" : ""}
Ort ${sicher(e.ort || "unbekannt")}
Gemeldet von ${e.erstellt_von
? sicher((e.ersteller_vorname || "") + " " + (e.ersteller_nachname || ""))
: sicher(quelleText[e.quelle] || "extern")}
${e.beschreibung ? `
${sicher(e.beschreibung).replace(/\n/g, " ")}
` : ""}
${e.pos_x ? `📍 Route setzen ` : ""}
${aktiv && !e.ichDabei ? `Einsatz übernehmen ` : ""}
${aktiv && e.ichDabei ? `Einsatz verlassen ` : ""}
${aktiv && e.ichDabei ? `Status: Vor Ort ` : ""}
${aktiv && d.rechte.schliessen ? `Abschließen ` : ""}
Einsatzverlauf
${aktiv ? `+ Notiz ` : ""}
${(e.notizen || []).length ? `
${e.notizen.map((n) =>
`
${zeit(n.am).slice(-5)}
${sicher(n.text)}
${sicher(n.dienstnummer || "")}
${sicher((n.firstname || "") + " " + (n.lastname || ""))}
`).join("")}
`
: `
Noch keine Einträge.
`}
Beteiligte Einheiten
${beteiligt.length}
${beteiligt.length ? `
${beteiligt.map((b) =>
`
${sicher(b.dienstnummer || "—")}
${sicher((b.firstname || "") + " " + (b.lastname || ""))}
seit ${zeit(b.beigetreten)}
`).join("")}
`
: `
Keine Einheit vor Ort.
`}
${aktiv && d.rechte.zuweisen ? `
Einheit anfordern
${d.beamteImDienst.map((b) =>
`
${sicher(b.dienstnummer)}
${sicher(b.name)}
${sicher(b.status)}
Anfordern
`).join("")}
ℹ
Die Einheit erhält eine Anfrage mit Wegpunkt. Ob sie übernimmt,
entscheidet sie selbst.
` : ""}
`;
/* ---- Aktionen ---- */
$("e-zurueck").addEventListener("click", () => { z.offenerEinsatz = null; rendern(behaelter); });
const route = $("e-route");
if (route) route.addEventListener("click", () =>
anClient("wegpunkt", { x: e.pos_x, y: e.pos_y }));
const beitreten = $("e-beitreten");
if (beitreten) beitreten.addEventListener("click", async () => {
try {
await aufruf("einsatz", "beitreten", { id: e.id });
meldung("Du hast den Einsatz übernommen.", "gut");
oeffneEinsatz(behaelter, e.id);
} catch (f) { meldung(f.message, "fehler"); }
});
const verlassen = $("e-verlassen");
if (verlassen) verlassen.addEventListener("click", async () => {
try {
await aufruf("einsatz", "verlassen", { id: e.id });
meldung("Du hast den Einsatz verlassen.");
oeffneEinsatz(behaelter, e.id);
} catch (f) { meldung(f.message, "fehler"); }
});
const vorort = $("e-vorort");
if (vorort) vorort.addEventListener("click", async () => {
try {
await aufruf("einsatz", "status", { id: e.id, status: "vorort" });
oeffneEinsatz(behaelter, e.id);
} catch (f) { meldung(f.message, "fehler"); }
});
const notiz = $("e-notiz");
if (notiz) notiz.addEventListener("click", () => {
fenster({
titel: "Notiz zum Einsatz",
inhalt: `Eintrag
Der Verlauf hilft später beim Bericht.
`,
knoepfe: [
{ text: "Abbrechen", klick: (_, zu) => zu() },
{ text: "Eintragen", klick: async (el, zu) => {
const text = el.querySelector("#en-text").value.trim();
if (text.length < 2) { meldung("Bitte einen Text eingeben.", "fehler"); return; }
try {
await aufruf("einsatz", "notiz", { id: e.id, text });
zu(); oeffneEinsatz(behaelter, e.id);
} catch (f) { meldung(f.message, "fehler"); }
}},
],
});
});
behaelter.querySelectorAll("[data-zuweisen]").forEach((b) =>
b.addEventListener("click", async () => {
try {
await aufruf("einsatz", "zuweisen", { id: e.id, quelle: +b.dataset.zuweisen });
meldung("Anfrage an die Einheit gesendet.", "gut");
} catch (f) { meldung(f.message, "fehler"); }
}));
const schliessen = $("e-schliessen");
if (schliessen) schliessen.addEventListener("click", () => {
bestaetigen({
titel: "Einsatz abschließen",
inhalt: `
${sicher(e.nummer)} — ${sicher(e.titel)}
Alle Beteiligungen werden beendet. Es wird festgehalten, dass du den Einsatz
abgeschlossen hast.
Abschlussvermerk
${d.rechte.bericht ? `
Berichtsentwurf vorbereiten — Titel, Beteiligte und der Einsatzverlauf
werden übernommen. Der Bericht bleibt ein Entwurf , bis du ihn ausarbeitest.
` : ""}`,
bestaetigenText: "Einsatz abschließen",
bestaetigt: async (el, zu) => {
const kasten = el.querySelector("#es-bericht");
try {
const ergebnis = await aufruf("einsatz", "abschliessen", {
id: e.id,
grund: el.querySelector("#es-grund").value.trim(),
berichtAnlegen: kasten ? kasten.checked : false,
});
zu();
meldung(ergebnis.berichtNummer
? "Einsatz abgeschlossen · Entwurf " + ergebnis.berichtNummer + " angelegt."
: "Einsatz abgeschlossen.", "gut");
z.offenerEinsatz = null;
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
}
async function oeffneEinsatz(behaelter, einsatzId) {
try {
const d = await aufruf("einsatz", "oeffnen", { id: einsatzId });
z.offenerEinsatz = einsatzId;
// Kategorien für die Anzeige mitführen
if (!z.daten) z.daten = { kategorien: [] };
d.kategorien = z.daten.kategorien;
einsatzAnsicht(behaelter, d);
} catch (f) { meldung(f.message, "fehler"); }
}
/* ---- Anlegen ------------------------------------------------------ */
function einsatzAnlegen(behaelter) {
const kategorien = (z.daten && z.daten.kategorien) || [];
fenster({
titel: "Einsatz anlegen",
inhalt: `
Titel
Kategorie
${kategorien.map((k) => `${sicher(k.name)} `).join("")}
Priorität
1 — höchste (sofort)
2 — hoch
3 — normal
4 — niedrig
Ort
Beschreibung
Meine aktuelle Position als Einsatzort übernehmen
`,
knoepfe: [
{ text: "Abbrechen", klick: (_, zu) => zu() },
{ text: "Einsatz anlegen", klick: async (el, zu) => {
try {
const ergebnis = await aufruf("einsatz", "erstellen", {
titel: el.querySelector("#ne-titel").value.trim(),
kategorie: el.querySelector("#ne-kategorie").value,
prioritaet: +el.querySelector("#ne-prio").value,
ort: el.querySelector("#ne-ort").value.trim(),
beschreibung: el.querySelector("#ne-text").value,
eigenePosition: el.querySelector("#ne-hier").checked,
});
zu();
meldung("Einsatz " + ergebnis.nummer + " angelegt.", "gut");
oeffneEinsatz(behaelter, ergebnis.id);
} catch (f) { meldung(f.message, "fehler"); }
}},
],
});
}
/* ---- Laden -------------------------------------------------------- */
async function rendern(behaelter) {
try {
z.daten = await aufruf("einsatz", "liste", { nurOffene: z.nurOffene });
// Markierungen im Spiel setzen
anClient("einsatzBlips", { einsaetze: z.daten.einsaetze });
if (z.offenerEinsatz) await oeffneEinsatz(behaelter, z.offenerEinsatz);
else uebersicht(behaelter);
} catch (f) {
behaelter.innerHTML =
`Einsätze
Konnte nicht geladen werden: ${sicher(f.message)}
`;
}
}
/* ---- Nachrichten vom Client -------------------------------------- */
window.addEventListener("message", (ereignis) => {
const n = ereignis.data || {};
// Eine Einheit wurde angefordert — der Beamte entscheidet selbst
if (n.aktion === "einsatzZuweisung") {
const d = n.daten || {};
bestaetigen({
titel: "Einsatz übernehmen?",
inhalt: `
${sicher(d.von)} bittet dich um Übernahme:
${sicher(d.nummer)} — ${sicher(d.titel)}
${d.ort ? "Ort: " + sicher(d.ort) : "Ort unbekannt"}
Ein Wegpunkt wurde bereits gesetzt.
`,
bestaetigenText: "Übernehmen",
bestaetigt: async (_, zu) => {
try {
await aufruf("einsatz", "beitreten", { id: d.id });
zu(); meldung("Einsatz übernommen.", "gut");
if (App.zustand.aktivesModul === "einsatz") rendern($("inhalt"));
} catch (f) { meldung(f.message, "fehler"); }
},
});
}
// Im Hintergrund hat sich etwas geändert
if (n.aktion === "einsatzAenderung" && App.zustand.aktivesModul === "einsatz") {
rendern($("inhalt"));
}
});
/* ---- Registrierung ------------------------------------------------ */
App.registriereAnsicht("einsatz", {
rendern,
aktualisieren: () => {
if (App.zustand.aktivesModul === "einsatz") rendern($("inhalt"));
},
});
})();