/* =====================================================================
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 ? `
+ Fahndung ausschreiben ` : ""}
Normal
${d.bestand.normal || 0}
Bewaffnet
${d.bestand.bewaffnet || 0}
Gefährlich
${d.bestand.gefaehrlich || 0}
Alle Arten
Personen
Fahrzeuge
Alle Stufen
Normal
Bewaffnet
Gefährlich
${z.archiv ? "Aktive zeigen" : "Archiv"}
${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)}
‹ Zurück
${aktiv && d.rechte.bearbeiten
? `✎ Bearbeiten ` : ""}
${aktiv && d.rechte.aufheben
? `Fahndung 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"
? `Akte öffnen ` : ""}
${ziel && f.typ === "fahrzeug"
? `Halterdaten ` : ""}
${!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
` : ""}
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.
Grund der Aufhebung
`,
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: `
Art der Fahndung
Person
Fahrzeug
Gefahrenstufe
Normal
Bewaffnet
Gefährlich
Priorität
1 — Sofort
2 — Hoch
3 — Normal
4 — Niedrig
Fahndungsgrund
Hinweise für die Streife
Fahndungsbild (Adresse)
ℹ
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: `
Gefahrenstufe
${["normal", "bewaffnet", "gefaehrlich"].map((g) =>
`${gefahrText[g]} `).join("")}
Priorität
${[1, 2, 3, 4].map((p) =>
`${p} — ${prioText[p]} `).join("")}
Fahndungsgrund
Hinweise
Fahndungsbild (Adresse)
`,
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 []; }
});
})();