/* ===================================================================== aether-police / Ansicht: Fahndungen Fahndungen werden von Beamten ausgeschrieben und von Beamten aufgehoben. Nichts davon geschieht von selbst. ===================================================================== */ "use strict"; (() => { const { sicher, zeit, aufruf, meldung, fenster, bestaetigen, $, anClient } = App; const z = { daten: null, offen: null, filterTyp: "", filterGefahr: "", archiv: false, suche: "", }; const gefahrArt = { normal: "neutral", bewaffnet: "warn", gefaehrlich: "kritisch" }; const gefahrText = { normal: "Normal", bewaffnet: "Bewaffnet", gefaehrlich: "Gefährlich" }; const prioText = { 1: "Sofort", 2: "Hoch", 3: "Normal", 4: "Niedrig" }; /* ---- Übersicht ---------------------------------------------------- */ function fahndungKarte(f) { return `
${f.bild ? `` : `${f.typ === "person" ? "👤" : "🚗"}`}
${sicher(f.bezeichnung)}
${sicher(f.grund)}
${sicher(f.dienstnummer || "")} ${sicher((f.firstname || "") + " " + (f.lastname || ""))} · ${zeit(f.erstellt_am)} ${!f.aktiv && f.aufgehoben_am ? ` · aufgehoben ${zeit(f.aufgehoben_am)} durch ${sicher((f.a_vorname || "") + " " + (f.a_nachname || ""))}` : ""}
${sicher(gefahrText[f.gefahr] || f.gefahr)} Prio ${sicher(prioText[f.prioritaet] || f.prioritaet)}
`; } function uebersicht(behaelter) { const d = z.daten; behaelter.innerHTML = `

Fahndungen

Werden von Beamten ausgeschrieben — und nur von Beamten wieder aufgehoben.

${d.rechte.erstellen ? `` : ""}
Normal
${d.bestand.normal || 0}
Bewaffnet
${d.bestand.bewaffnet || 0}
Gefährlich
${d.bestand.gefaehrlich || 0}
${d.fahndungen.length ? `
${d.fahndungen.map(fahndungKarte).join("")}
` : `
${z.archiv ? "Keine aufgehobenen Fahndungen." : "Keine aktiven Fahndungen."}
`}
`; const neu = $("fa-neu"); if (neu) neu.addEventListener("click", () => ausschreibenDialog(behaelter)); const suchfeld = $("fa-suche"); suchfeld.addEventListener("keydown", (e) => { if (e.key === "Enter") { z.suche = suchfeld.value.trim(); rendern(behaelter); } }); $("fa-typ").addEventListener("change", (e) => { z.filterTyp = e.target.value; rendern(behaelter); }); $("fa-gefahr").addEventListener("change", (e) => { z.filterGefahr = e.target.value; rendern(behaelter); }); $("fa-archiv").addEventListener("click", () => { z.archiv = !z.archiv; rendern(behaelter); }); behaelter.querySelectorAll("[data-fahndung]").forEach((el) => el.addEventListener("click", () => oeffne(behaelter, +el.dataset.fahndung))); } /* ---- Einzelansicht ------------------------------------------------ */ function einzelAnsicht(behaelter, d) { const f = d.fahndung; const ziel = f.ziel; const aktiv = f.aktiv === 1; behaelter.innerHTML = `

${f.typ === "person" ? "👤" : "🚗"} ${sicher(f.bezeichnung)}

Fahndung #${f.id} · ${sicher(gefahrText[f.gefahr] || f.gefahr)} · Priorität ${sicher(prioText[f.prioritaet] || f.prioritaet)}

${aktiv && d.rechte.bearbeiten ? `` : ""} ${aktiv && d.rechte.aufheben ? `` : ""}
${!aktiv ? `
✓ Diese Fahndung wurde am ${zeit(f.aufgehoben_am)} von ${sicher((f.a_vorname || "") + " " + (f.a_nachname || ""))} aufgehoben.
` : f.gefahr !== "normal" ? `
⚠ Gefahrenstufe ${sicher(gefahrText[f.gefahr])}. ${f.gefahr === "gefaehrlich" ? "Zugriff nur mit ausreichender Verstärkung." : "Es ist mit Bewaffnung zu rechnen."}
` : ""}
Fahndungsgrund
${sicher(f.grund).replace(/\n/g, "
")}
${f.hinweise ? `
Hinweise für die Streife
${sicher(f.hinweise).replace(/\n/g, "
")}
` : ""}
Ausgeschrieben${sicher(f.dienstnummer || "")} ${sicher((f.firstname || "") + " " + (f.lastname || ""))} Am${zeit(f.erstellt_am)}
${f.typ === "person" ? "Gesuchte Person" : "Gesuchtes Fahrzeug"} ${ziel && f.typ === "person" ? `` : ""} ${ziel && f.typ === "fahrzeug" ? `` : ""}
${!ziel ? `
Das Ziel ist nicht mehr im System auffindbar.
` : f.typ === "person" ? `
${ziel.lichtbild ? `` : `Kein Lichtbild`}
Name${sicher(ziel.firstname + " " + ziel.lastname)} Geboren${sicher(String(ziel.dob || "").slice(0, 10))} Warnhinweis${sicher(ziel.warnhinweis || "—")}
` : `
Kennzeichen${sicher(ziel.plate)} Modell${sicher(ziel.name || ziel.model)} Farbe${ziel.farbe ? ` ${sicher(ziel.farbe)}` : "—"} Halter${sicher((ziel.firstname || "unbekannt") + " " + (ziel.lastname || ""))} Status${sicher(ziel.state === "impound" ? "sichergestellt" : ziel.state === "out" ? "unterwegs" : "eingeparkt")}
`}
${f.bild ? `
Fahndungsbild
Fahndungsbild
` : ""}
Vorgangshistorie
${d.historie.length ? `
${d.historie.map((h) => `
${sicher(h.aktion)}
${sicher(h.dienstnummer || "")} ${sicher((h.firstname || "") + " " + (h.lastname || ""))} · ${zeit(h.am)} ${h.grund ? " · " + sicher(h.grund) : ""}
`).join("")}
` : `
Keine Einträge.
`}
`; $("fa-zurueck").addEventListener("click", () => { z.offen = null; rendern(behaelter); }); // Sprünge ins MDT — die Akte wird dort direkt geöffnet behaelter.querySelectorAll("[data-person]").forEach((el) => el.addEventListener("click", () => App.oeffneModul("mdt", { person: el.dataset.person }))); behaelter.querySelectorAll("[data-fahrzeug]").forEach((el) => el.addEventListener("click", () => App.oeffneModul("mdt", { fahrzeug: el.dataset.fahrzeug }))); const bearbeiten = $("fa-bearbeiten"); if (bearbeiten) bearbeiten.addEventListener("click", () => bearbeitenDialog(behaelter, f)); const aufheben = $("fa-aufheben"); if (aufheben) aufheben.addEventListener("click", () => { bestaetigen({ titel: "Fahndung aufheben", inhalt: `

${sicher(f.bezeichnung)}
${sicher(f.grund)}

📄
Die Fahndung wandert ins Archiv und bleibt dort nachvollziehbar. Es wird festgehalten, dass du sie aufgehoben hast.
`, bestaetigenText: "Fahndung aufheben", bestaetigt: async (el, zu) => { try { await aufruf("fahndung", "aufheben", { id: f.id, grund: el.querySelector("#fh-grund").value.trim(), }); zu(); meldung("Fahndung aufgehoben.", "gut"); z.offen = null; rendern(behaelter); } catch (fehler) { meldung(fehler.message, "fehler"); } }, }); }); } /* ---- Dialoge ------------------------------------------------------ */ function ausschreibenDialog(behaelter) { let gewaehlt = null; fenster({ titel: "Fahndung ausschreiben", inhalt: `
Noch kein Ziel ausgewählt.
ℹ
Die Gefahrenstufe bestimmt, wie dringlich alle Beamten benachrichtigt werden. Bei gefährlich geht ein kritischer Hinweis an alle im Dienst.
`, knoepfe: [ { text: "Abbrechen", klick: (_, zu) => zu() }, { text: "Ausschreiben", klick: async (el, zu) => { if (!gewaehlt) { meldung("Bitte ein Ziel auswählen.", "fehler"); return; } try { const ergebnis = await aufruf("fahndung", "erstellen", { typ: el.querySelector("#fn-typ").value, zielId: gewaehlt, gefahr: el.querySelector("#fn-gefahr").value, prioritaet: +el.querySelector("#fn-prio").value, grund: el.querySelector("#fn-grund").value.trim(), hinweise: el.querySelector("#fn-hinweise").value.trim(), bild: el.querySelector("#fn-bild").value.trim(), }); zu(); meldung("Fahndung ausgeschrieben: " + ergebnis.bezeichnung, "gut"); oeffne(behaelter, ergebnis.id); } catch (fehler) { meldung(fehler.message, "fehler"); } }}, ], nachOeffnen: (el) => { const suchen = async () => { const begriff = el.querySelector("#fn-suche").value.trim(); if (begriff.length < 2) { meldung("Bitte mindestens zwei Zeichen.", "fehler"); return; } try { const d = await aufruf("fahndung", "zielSuche", { typ: el.querySelector("#fn-typ").value, begriff, }); const anzeige = el.querySelector("#fn-gewaehlt"); anzeige.innerHTML = (d.treffer || []).length ? `
${d.treffer.slice(0, 6).map((t) => `
${sicher(t.titel)}
${sicher(t.unter)}
`).join("")}
` : `
Keine Treffer.
`; anzeige.querySelectorAll("[data-wahl]").forEach((zeile) => zeile.addEventListener("click", () => { gewaehlt = zeile.dataset.wahl; anzeige.innerHTML = `Ausgewählt: ${sicher(zeile.dataset.titel)}`; })); } catch (fehler) { meldung(fehler.message, "fehler"); } }; el.querySelector("#fn-suchen").addEventListener("click", suchen); el.querySelector("#fn-suche").addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); suchen(); } }); // Bei Wechsel der Art die Auswahl verwerfen el.querySelector("#fn-typ").addEventListener("change", () => { gewaehlt = null; el.querySelector("#fn-gewaehlt").textContent = "Noch kein Ziel ausgewählt."; }); }, }); } function bearbeitenDialog(behaelter, f) { fenster({ titel: "Fahndung bearbeiten", inhalt: `
`, knoepfe: [ { text: "Abbrechen", klick: (_, zu) => zu() }, { text: "Speichern", klick: async (el, zu) => { try { await aufruf("fahndung", "bearbeiten", { id: f.id, gefahr: el.querySelector("#fb-gefahr").value, prioritaet: +el.querySelector("#fb-prio").value, grund: el.querySelector("#fb-grund").value.trim(), hinweise: el.querySelector("#fb-hinweise").value.trim(), bild: el.querySelector("#fb-bild").value.trim(), }); zu(); meldung("Gespeichert.", "gut"); oeffne(behaelter, f.id); } catch (fehler) { meldung(fehler.message, "fehler"); } }}, ], }); } /* ---- Laden -------------------------------------------------------- */ async function oeffne(behaelter, fahndungId) { try { const d = await aufruf("fahndung", "oeffnen", { id: fahndungId }); z.offen = fahndungId; einzelAnsicht(behaelter, d); } catch (f) { meldung(f.message, "fehler"); } } async function rendern(behaelter) { try { z.daten = await aufruf("fahndung", "liste", { typ: z.filterTyp, gefahr: z.filterGefahr, archiv: z.archiv, begriff: z.suche, }); if (z.offen) await oeffne(behaelter, z.offen); else uebersicht(behaelter); } catch (f) { behaelter.innerHTML = `

Fahndungen

Konnte nicht geladen werden: ${sicher(f.message)}

`; } } App.registriereAnsicht("fahndung", { rendern, aktualisieren: () => { if (App.zustand.aktivesModul === "fahndung") rendern($("inhalt")); }, }); App.registriereSuche(async (begriff) => { try { const d = await aufruf("fahndung", "liste", { begriff }); return (d.fahndungen || []).slice(0, 5).map((f) => ({ bereich: "Fahndung", titel: f.bezeichnung, unter: f.grund + " · " + (gefahrText[f.gefahr] || f.gefahr), })); } catch (e) { return []; } }); })();