- 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>
459 lines
20 KiB
JavaScript
459 lines
20 KiB
JavaScript
/* =====================================================================
|
||
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 `
|
||
<div class="fahndung-karte gefahr-${sicher(f.gefahr)}" data-fahndung="${f.id}">
|
||
<div class="fk-bild">
|
||
${f.bild ? `<img src="${sicher(f.bild)}" alt="">`
|
||
: `<span class="fk-symbol">${f.typ === "person" ? "👤" : "🚗"}</span>`}
|
||
</div>
|
||
<div class="fk-haupt">
|
||
<div class="fk-titel">${sicher(f.bezeichnung)}</div>
|
||
<div class="fk-grund">${sicher(f.grund)}</div>
|
||
<div class="fk-meta">
|
||
${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 || ""))}` : ""}
|
||
</div>
|
||
</div>
|
||
<div class="fk-rechts">
|
||
<span class="marker ${gefahrArt[f.gefahr]}">${sicher(gefahrText[f.gefahr] || f.gefahr)}</span>
|
||
<span class="fk-prio">Prio ${sicher(prioText[f.prioritaet] || f.prioritaet)}</span>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function uebersicht(behaelter) {
|
||
const d = z.daten;
|
||
|
||
behaelter.innerHTML = `
|
||
<div class="seiten-kopf">
|
||
<div>
|
||
<h2>Fahndungen</h2>
|
||
<p>Werden von Beamten ausgeschrieben — und nur von Beamten wieder aufgehoben.</p>
|
||
</div>
|
||
${d.rechte.erstellen ? `<button class="knopf klein" id="fa-neu">+ Fahndung ausschreiben</button>` : ""}
|
||
</div>
|
||
|
||
<div class="raster k3" style="margin-bottom:14px">
|
||
<div class="kennzahl"><div class="kz-titel">Normal</div>
|
||
<div class="kz-wert">${d.bestand.normal || 0}</div></div>
|
||
<div class="kennzahl warn"><div class="kz-titel">Bewaffnet</div>
|
||
<div class="kz-wert">${d.bestand.bewaffnet || 0}</div></div>
|
||
<div class="kennzahl hoch"><div class="kz-titel">Gefährlich</div>
|
||
<div class="kz-wert">${d.bestand.gefaehrlich || 0}</div></div>
|
||
</div>
|
||
|
||
<div class="mdt-suchleiste" style="margin-bottom:14px">
|
||
<input id="fa-suche" type="search" placeholder="Bezeichnung oder Grund …"
|
||
value="${sicher(z.suche)}">
|
||
<select id="fa-typ" class="katalog-suche">
|
||
<option value="">Alle Arten</option>
|
||
<option value="person" ${z.filterTyp === "person" ? "selected" : ""}>Personen</option>
|
||
<option value="fahrzeug" ${z.filterTyp === "fahrzeug" ? "selected" : ""}>Fahrzeuge</option>
|
||
</select>
|
||
<select id="fa-gefahr" class="katalog-suche">
|
||
<option value="">Alle Stufen</option>
|
||
<option value="normal" ${z.filterGefahr === "normal" ? "selected" : ""}>Normal</option>
|
||
<option value="bewaffnet" ${z.filterGefahr === "bewaffnet" ? "selected" : ""}>Bewaffnet</option>
|
||
<option value="gefaehrlich" ${z.filterGefahr === "gefaehrlich" ? "selected" : ""}>Gefährlich</option>
|
||
</select>
|
||
<button class="knopf leise klein" id="fa-archiv">
|
||
${z.archiv ? "Aktive zeigen" : "Archiv"}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="karte">
|
||
${d.fahndungen.length
|
||
? `<div class="fahndungen">${d.fahndungen.map(fahndungKarte).join("")}</div>`
|
||
: `<div class="liste-leer">${z.archiv ? "Keine aufgehobenen Fahndungen." : "Keine aktiven Fahndungen."}</div>`}
|
||
</div>`;
|
||
|
||
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 = `
|
||
<div class="seiten-kopf">
|
||
<div>
|
||
<h2>${f.typ === "person" ? "👤" : "🚗"} ${sicher(f.bezeichnung)}</h2>
|
||
<p>Fahndung #${f.id} · ${sicher(gefahrText[f.gefahr] || f.gefahr)}
|
||
· Priorität ${sicher(prioText[f.prioritaet] || f.prioritaet)}</p>
|
||
</div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||
<button class="knopf leise klein" id="fa-zurueck">‹ Zurück</button>
|
||
${aktiv && d.rechte.bearbeiten
|
||
? `<button class="knopf leise klein" id="fa-bearbeiten">✎ Bearbeiten</button>` : ""}
|
||
${aktiv && d.rechte.aufheben
|
||
? `<button class="knopf klein" id="fa-aufheben">Fahndung aufheben</button>` : ""}
|
||
</div>
|
||
</div>
|
||
|
||
${!aktiv ? `
|
||
<div class="warnung niedrig">
|
||
<span class="w-symbol">✓</span>
|
||
<span>Diese Fahndung wurde am ${zeit(f.aufgehoben_am)} von
|
||
<b>${sicher((f.a_vorname || "") + " " + (f.a_nachname || ""))}</b> aufgehoben.</span>
|
||
</div>`
|
||
: f.gefahr !== "normal" ? `
|
||
<div class="warnung hoch">
|
||
<span class="w-symbol">⚠</span>
|
||
<span><b>Gefahrenstufe ${sicher(gefahrText[f.gefahr])}.</b>
|
||
${f.gefahr === "gefaehrlich"
|
||
? "Zugriff nur mit ausreichender Verstärkung."
|
||
: "Es ist mit Bewaffnung zu rechnen."}</span>
|
||
</div>` : ""}
|
||
|
||
<div class="raster k2" style="margin-top:14px">
|
||
<div class="karte">
|
||
<div class="karte-kopf"><span class="karte-titel">Fahndungsgrund</span></div>
|
||
<div class="akte-inhalt">${sicher(f.grund).replace(/\n/g, "<br>")}</div>
|
||
|
||
${f.hinweise ? `
|
||
<div class="karte-titel" style="margin-top:16px">Hinweise für die Streife</div>
|
||
<div class="akte-inhalt" style="margin-top:8px">${sicher(f.hinweise).replace(/\n/g, "<br>")}</div>
|
||
` : ""}
|
||
|
||
<div class="akte-zeilen" style="grid-template-columns:auto 1fr;margin-top:16px">
|
||
<span>Ausgeschrieben</span><b>${sicher(f.dienstnummer || "")}
|
||
${sicher((f.firstname || "") + " " + (f.lastname || ""))}</b>
|
||
<span>Am</span><b>${zeit(f.erstellt_am)}</b>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="karte">
|
||
<div class="karte-kopf">
|
||
<span class="karte-titel">${f.typ === "person" ? "Gesuchte Person" : "Gesuchtes Fahrzeug"}</span>
|
||
${ziel && f.typ === "person"
|
||
? `<button class="knopf leise klein" data-person="${sicher(f.ziel_id)}">Akte öffnen</button>` : ""}
|
||
${ziel && f.typ === "fahrzeug"
|
||
? `<button class="knopf leise klein" data-fahrzeug="${sicher(f.ziel_id)}">Halterdaten</button>` : ""}
|
||
</div>
|
||
|
||
${!ziel ? `<div class="liste-leer">Das Ziel ist nicht mehr im System auffindbar.</div>`
|
||
: f.typ === "person" ? `
|
||
<div style="display:flex;gap:14px">
|
||
<div class="akte-bild" style="width:90px;height:112px">
|
||
${ziel.lichtbild ? `<img src="${sicher(ziel.lichtbild)}" alt="">`
|
||
: `<span class="kein-bild">Kein Lichtbild</span>`}
|
||
</div>
|
||
<div class="akte-zeilen" style="grid-template-columns:auto 1fr;flex:1">
|
||
<span>Name</span><b>${sicher(ziel.firstname + " " + ziel.lastname)}</b>
|
||
<span>Geboren</span><b>${sicher(String(ziel.dob || "").slice(0, 10))}</b>
|
||
<span>Warnhinweis</span><b>${sicher(ziel.warnhinweis || "—")}</b>
|
||
</div>
|
||
</div>`
|
||
: `<div class="akte-zeilen" style="grid-template-columns:auto 1fr">
|
||
<span>Kennzeichen</span><b>${sicher(ziel.plate)}</b>
|
||
<span>Modell</span><b>${sicher(ziel.name || ziel.model)}</b>
|
||
<span>Farbe</span><b>${ziel.farbe
|
||
? `<span class="farbtupfer" style="background:${sicher(ziel.farbe)}"></span> ${sicher(ziel.farbe)}`
|
||
: "—"}</b>
|
||
<span>Halter</span><b>${sicher((ziel.firstname || "unbekannt") + " " + (ziel.lastname || ""))}</b>
|
||
<span>Status</span><b>${sicher(ziel.state === "impound" ? "sichergestellt"
|
||
: ziel.state === "out" ? "unterwegs" : "eingeparkt")}</b>
|
||
</div>`}
|
||
</div>
|
||
|
||
${f.bild ? `
|
||
<div class="karte" style="grid-column:1/-1">
|
||
<div class="karte-kopf"><span class="karte-titel">Fahndungsbild</span></div>
|
||
<img src="${sicher(f.bild)}" alt="Fahndungsbild" class="fahndung-bild">
|
||
</div>` : ""}
|
||
|
||
<div class="karte" style="grid-column:1/-1">
|
||
<div class="karte-kopf"><span class="karte-titel">Vorgangshistorie</span></div>
|
||
${d.historie.length ? `<div class="liste">${d.historie.map((h) => `
|
||
<div class="liste-zeile">
|
||
<div class="lz-haupt">
|
||
<div class="lz-titel">${sicher(h.aktion)}</div>
|
||
<div class="lz-unter">${sicher(h.dienstnummer || "")}
|
||
${sicher((h.firstname || "") + " " + (h.lastname || ""))} · ${zeit(h.am)}
|
||
${h.grund ? " · " + sicher(h.grund) : ""}</div>
|
||
</div>
|
||
</div>`).join("")}</div>`
|
||
: `<div class="liste-leer">Keine Einträge.</div>`}
|
||
</div>
|
||
</div>`;
|
||
|
||
$("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: `
|
||
<p style="margin:0 0 12px;font-size:13px;line-height:1.6">
|
||
<b>${sicher(f.bezeichnung)}</b><br>${sicher(f.grund)}
|
||
</p>
|
||
<div class="folgen">
|
||
<div class="folge">
|
||
<span class="folge-symbol">📄</span>
|
||
<div>Die Fahndung wandert ins Archiv und bleibt dort nachvollziehbar.
|
||
Es wird festgehalten, dass <b>du</b> sie aufgehoben hast.</div>
|
||
</div>
|
||
</div>
|
||
<div class="feld" style="margin-top:12px">
|
||
<label>Grund der Aufhebung</label>
|
||
<input id="fh-grund" placeholder="z. B. Person gestellt, Sachverhalt geklärt">
|
||
</div>`,
|
||
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: `
|
||
<div class="feld"><label>Art der Fahndung</label>
|
||
<select id="fn-typ">
|
||
<option value="person">Person</option>
|
||
<option value="fahrzeug">Fahrzeug</option>
|
||
</select></div>
|
||
|
||
<div class="feld">
|
||
<label>Ziel suchen</label>
|
||
<div class="person-waehler">
|
||
<input id="fn-suche" placeholder="Name oder Kennzeichen">
|
||
<button class="knopf leise klein" id="fn-suchen">Suchen</button>
|
||
</div>
|
||
<div class="feld-hinweis" id="fn-gewaehlt">Noch kein Ziel ausgewählt.</div>
|
||
</div>
|
||
|
||
<div class="raster k2" style="gap:10px">
|
||
<div class="feld" style="margin:0"><label>Gefahrenstufe</label>
|
||
<select id="fn-gefahr">
|
||
<option value="normal">Normal</option>
|
||
<option value="bewaffnet">Bewaffnet</option>
|
||
<option value="gefaehrlich">Gefährlich</option>
|
||
</select></div>
|
||
<div class="feld" style="margin:0"><label>Priorität</label>
|
||
<select id="fn-prio">
|
||
<option value="1">1 — Sofort</option>
|
||
<option value="2">2 — Hoch</option>
|
||
<option value="3" selected>3 — Normal</option>
|
||
<option value="4">4 — Niedrig</option>
|
||
</select></div>
|
||
</div>
|
||
|
||
<div class="feld"><label>Fahndungsgrund</label>
|
||
<textarea id="fn-grund" placeholder="Weshalb wird gefahndet?"></textarea></div>
|
||
<div class="feld"><label>Hinweise für die Streife</label>
|
||
<textarea id="fn-hinweise" placeholder="Verhalten, letzte Sichtung, Vorgehensweise"></textarea></div>
|
||
<div class="feld"><label>Fahndungsbild (Adresse)</label>
|
||
<input id="fn-bild" placeholder="https://… (optional)"></div>
|
||
|
||
<div class="grundsatz">
|
||
<span>ℹ</span>
|
||
<div>Die Gefahrenstufe bestimmt, wie dringlich alle Beamten benachrichtigt werden.
|
||
Bei <b>gefährlich</b> geht ein kritischer Hinweis an alle im Dienst.</div>
|
||
</div>`,
|
||
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
|
||
? `<div class="liste" style="margin-top:6px">${d.treffer.slice(0, 6).map((t) =>
|
||
`<div class="liste-zeile treffer" data-wahl="${sicher(t.id)}"
|
||
data-titel="${sicher(t.titel)}">
|
||
<div class="lz-haupt"><div class="lz-titel">${sicher(t.titel)}</div>
|
||
<div class="lz-unter">${sicher(t.unter)}</div></div>
|
||
</div>`).join("")}</div>`
|
||
: `<div class="liste-leer">Keine Treffer.</div>`;
|
||
|
||
anzeige.querySelectorAll("[data-wahl]").forEach((zeile) =>
|
||
zeile.addEventListener("click", () => {
|
||
gewaehlt = zeile.dataset.wahl;
|
||
anzeige.innerHTML = `Ausgewählt: <b>${sicher(zeile.dataset.titel)}</b>`;
|
||
}));
|
||
} 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: `
|
||
<div class="raster k2" style="gap:10px">
|
||
<div class="feld" style="margin:0"><label>Gefahrenstufe</label>
|
||
<select id="fb-gefahr">
|
||
${["normal", "bewaffnet", "gefaehrlich"].map((g) =>
|
||
`<option value="${g}" ${f.gefahr === g ? "selected" : ""}>${gefahrText[g]}</option>`).join("")}
|
||
</select></div>
|
||
<div class="feld" style="margin:0"><label>Priorität</label>
|
||
<select id="fb-prio">
|
||
${[1, 2, 3, 4].map((p) =>
|
||
`<option value="${p}" ${f.prioritaet === p ? "selected" : ""}>${p} — ${prioText[p]}</option>`).join("")}
|
||
</select></div>
|
||
</div>
|
||
<div class="feld"><label>Fahndungsgrund</label>
|
||
<textarea id="fb-grund">${sicher(f.grund)}</textarea></div>
|
||
<div class="feld"><label>Hinweise</label>
|
||
<textarea id="fb-hinweise">${sicher(f.hinweise || "")}</textarea></div>
|
||
<div class="feld"><label>Fahndungsbild (Adresse)</label>
|
||
<input id="fb-bild" value="${sicher(f.bild || "")}"></div>`,
|
||
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 = `<div class="seiten-kopf"><div><h2>Fahndungen</h2>
|
||
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
|
||
}
|
||
}
|
||
|
||
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 []; }
|
||
});
|
||
})();
|