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

603 lines
26 KiB
JavaScript
Raw 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-police / Ansicht: Berichtswesen
Drei Stufen: Entwurf → Eingereicht → Freigegeben. Keine davon wird
automatisch erreicht. Unterschriften sind verbindlich.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, aufruf, meldung, fenster, bestaetigen, $ } = App;
const z = {
daten: null,
offen: null,
filterStatus: "",
nurEigene: false,
suche: "",
};
const statusArt = { entwurf: "warn", eingereicht: "info", freigegeben: "gut" };
const statusText = { entwurf: "Entwurf", eingereicht: "Eingereicht", freigegeben: "Freigegeben" };
/* ---- Übersicht ---------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>Berichte</h2>
<p>Entwurf → Eingereicht → Freigegeben. Jede Stufe verlangt eine Entscheidung.</p>
</div>
${d.rechte.erstellen ? `<button class="knopf klein" id="br-neu">+ Bericht anlegen</button>` : ""}
</div>
<div class="raster k2" style="margin-bottom:14px">
<div class="kennzahl ${d.eigeneOffen > 0 ? "warn" : ""}">
<div class="kz-titel">Meine offenen Entwürfe</div>
<div class="kz-wert">${d.eigeneOffen}</div>
<div class="kz-zusatz">warten auf Ausarbeitung</div>
</div>
<div class="kennzahl ${d.zurPruefung > 0 ? "info" : ""}">
<div class="kz-titel">Zur Prüfung eingereicht</div>
<div class="kz-wert">${d.zurPruefung}</div>
<div class="kz-zusatz">${d.rechte.freigeben ? "warten auf deine Freigabe" : "in Bearbeitung"}</div>
</div>
</div>
<div class="mdt-suchleiste" style="margin-bottom:14px">
<input id="br-suche" type="search" placeholder="Nummer oder Titel …" value="${sicher(z.suche)}">
<select id="br-status" class="katalog-suche">
<option value="">Alle Stufen</option>
<option value="entwurf" ${z.filterStatus === "entwurf" ? "selected" : ""}>Entwürfe</option>
<option value="eingereicht" ${z.filterStatus === "eingereicht" ? "selected" : ""}>Eingereicht</option>
<option value="freigegeben" ${z.filterStatus === "freigegeben" ? "selected" : ""}>Freigegeben</option>
</select>
<button class="knopf leise klein" id="br-eigene">
${z.nurEigene ? "Alle anzeigen" : "Nur meine"}
</button>
</div>
<div class="karte">
${d.berichte.length ? `<div class="liste">${d.berichte.map((b) => `
<div class="liste-zeile treffer" data-bericht="${b.id}">
<span class="prio ${b.status === "entwurf" ? "p2" : b.status === "eingereicht" ? "p3" : "p4"}"></span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(b.titel)}
${b.eigener ? '<span class="marker neutral">eigener</span>' : ""}</div>
<div class="lz-unter">
${sicher(b.nummer)}
${b.einsatz_nummer ? " · Einsatz " + sicher(b.einsatz_nummer) : ""}
· ${sicher(b.dienstnummer || "")} ${sicher((b.firstname || "") + " " + (b.lastname || ""))}
· ${zeit(b.erstellt_am)}
</div>
</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:5px">
<span class="marker ${statusArt[b.status]}">${sicher(statusText[b.status] || b.status)}</span>
${b.unterschriften.length
? `<span class="lz-unter">✍ ${b.unterschriften.length}</span>` : ""}
</div>
</div>`).join("")}</div>`
: `<div class="liste-leer">Keine Berichte gefunden.</div>`}
</div>`;
const neu = $("br-neu");
if (neu) neu.addEventListener("click", () => anlegenDialog(behaelter));
const suchfeld = $("br-suche");
suchfeld.addEventListener("keydown", (e) => {
if (e.key === "Enter") { z.suche = suchfeld.value.trim(); rendern(behaelter); }
});
$("br-status").addEventListener("change", (e) => {
z.filterStatus = e.target.value; rendern(behaelter);
});
$("br-eigene").addEventListener("click", () => {
z.nurEigene = !z.nurEigene; rendern(behaelter);
});
behaelter.querySelectorAll("[data-bericht]").forEach((el) =>
el.addEventListener("click", () => oeffne(behaelter, +el.dataset.bericht)));
}
/* ---- Einzelansicht ------------------------------------------------ */
function einzelAnsicht(behaelter, d) {
const b = d.bericht;
const bearbeitbar = d.rechte.bearbeiten;
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>${sicher(b.titel)}</h2>
<p>${sicher(b.nummer)}
${b.einsatz_nummer ? " · Einsatz " + sicher(b.einsatz_nummer) : ""}
${b.aktenzeichen ? " · Akte " + sicher(b.aktenzeichen) : ""}</p>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="knopf leise klein" id="br-zurueck">‹ Zurück</button>
${bearbeitbar ? `<button class="knopf leise klein" id="br-bearbeiten">✎ Bearbeiten</button>` : ""}
${!d.habeUnterschrieben && b.status !== "freigegeben"
? `<button class="knopf leise klein" id="br-unterschreiben">✍ Unterschreiben</button>` : ""}
${b.status === "entwurf" && bearbeitbar
? `<button class="knopf klein" id="br-einreichen">Einreichen</button>` : ""}
${b.status === "eingereicht" && d.rechte.freigeben
? `<button class="knopf leise klein" id="br-zurueckweisen">Nachbessern</button>
<button class="knopf klein" id="br-freigeben">Freigeben</button>` : ""}
</div>
</div>
${b.status === "freigegeben" ? `
<div class="warnung niedrig">
<span class="w-symbol">🔒</span>
<span>Dieser Bericht ist <b>freigegeben</b> und damit inhaltlich gesperrt —
auch für den Verfasser.</span>
</div>` : ""}
<div class="raster k2" style="margin-top:14px">
<div class="karte" style="grid-column:1/-1">
<div class="karte-kopf">
<span class="karte-titel">Sachverhalt</span>
<span class="marker ${statusArt[b.status]}">${sicher(statusText[b.status] || b.status)}</span>
</div>
<div class="akte-zeilen" style="grid-template-columns:auto 1fr;margin-bottom:12px">
<span>Verfasser</span><b>${sicher(b.dienstnummer || "")}
${sicher((b.firstname || "") + " " + (b.lastname || ""))}</b>
<span>Angelegt</span><b>${zeit(b.erstellt_am)}</b>
</div>
<div class="akte-inhalt">${b.beschreibung
? sicher(b.beschreibung).replace(/\n/g, "<br>")
: "<i>Noch keine Beschreibung erfasst.</i>"}</div>
<div class="karte-titel" style="margin-top:18px">Getroffene Maßnahmen</div>
<div class="akte-inhalt" style="margin-top:8px">${b.massnahmen
? sicher(b.massnahmen).replace(/\n/g, "<br>")
: "<i>Keine Maßnahmen dokumentiert.</i>"}</div>
</div>
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Beteiligte</span>
${bearbeitbar ? `<button class="knopf leise klein" id="br-beteiligt">+</button>` : ""}
</div>
${b.beteiligte.length ? `<div class="liste">${b.beteiligte.map((p, i) => `
<div class="liste-zeile">
<span class="marker ${p.typ === "beamter" ? "info" : "neutral"}">
${p.typ === "beamter" ? (p.dienstnummer || "Beamter") : "Person"}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(p.name)}</div>
${p.rolle ? `<div class="lz-unter">${sicher(p.rolle)}</div>` : ""}
</div>
${bearbeitbar && p.charakterId !== b.erstellt_von
? `<button class="knopf leise klein" data-bet-weg="${i + 1}">✕</button>` : ""}
</div>`).join("")}</div>`
: `<div class="liste-leer">Keine Beteiligten erfasst.</div>`}
</div>
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Anlagen & Beweise</span>
${bearbeitbar ? `<button class="knopf leise klein" id="br-anlage">+</button>` : ""}
</div>
${b.anlagen.length ? `<div class="liste">${b.anlagen.map((a, i) => `
<div class="liste-zeile">
<span class="marker ${a.typ === "beweis" ? "info" : "neutral"}">
${a.typ === "beweis" ? sicher(a.kennung) : "Anlage"}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(a.titel)}</div>
${a.adresse ? `<div class="lz-unter">${sicher(a.adresse)}</div>` : ""}
</div>
${a.adresse
? `<a class="knopf leise klein" href="${sicher(a.adresse)}" target="_blank"
style="text-decoration:none">Öffnen</a>` : ""}
${bearbeitbar ? `<button class="knopf leise klein" data-anl-weg="${i + 1}">✕</button>` : ""}
</div>`).join("")}</div>`
: `<div class="liste-leer">Keine Anlagen.</div>`}
</div>
<div class="karte" style="grid-column:1/-1">
<div class="karte-kopf">
<span class="karte-titel">Unterschriften</span>
<span class="marker neutral">${b.unterschriften.length}</span>
</div>
${b.unterschriften.length ? `<div class="unterschriften">${b.unterschriften.map((u) => `
<div class="unterschrift">
<div class="us-name">${sicher(u.name)}</div>
<div class="us-meta">${sicher(u.dienstnummer || "")} · ${sicher(u.rolle || "")}</div>
<div class="us-zeit">${zeit(u.am)}${u.freigabe ? " · Freigabe" : ""}</div>
</div>`).join("")}</div>`
: `<div class="liste-leer">Noch nicht unterschrieben.</div>`}
<div class="grundsatz">
<span>ℹ</span>
<div>Eine Unterschrift ist <b>verbindlich</b> und wird nicht zurückgenommen.
Sie bestätigt, dass der Inhalt aus deiner Sicht zutrifft.</div>
</div>
</div>
</div>`;
verdrahteEinzel(behaelter, d);
}
function verdrahteEinzel(behaelter, d) {
const b = d.bericht;
$("br-zurueck").addEventListener("click", () => { z.offen = null; rendern(behaelter); });
const bearbeiten = $("br-bearbeiten");
if (bearbeiten) bearbeiten.addEventListener("click", () => {
const unterschrieben = b.unterschriften.length > 0;
fenster({
titel: "Bericht bearbeiten",
inhalt: `
<div class="feld"><label>Titel</label>
<input id="be-titel" value="${sicher(b.titel)}"></div>
<div class="feld"><label>Sachverhalt</label>
<textarea id="be-text" style="min-height:170px">${sicher(b.beschreibung || "")}</textarea></div>
<div class="feld"><label>Getroffene Maßnahmen</label>
<textarea id="be-mass" style="min-height:110px">${sicher(b.massnahmen || "")}</textarea></div>
${unterschrieben ? `
<div class="abweichung">
<div class="abweichung-kopf">⚠ Der Bericht wurde bereits unterschrieben</div>
<div class="feld" style="margin:10px 0 0">
<label>Grund der Änderung (erforderlich)</label>
<input id="be-grund" placeholder="Warum wird nachträglich geändert?">
</div>
</div>` : ""}`,
knoepfe: [
{ text: "Abbrechen", klick: (_, zu) => zu() },
{ text: "Speichern", klick: async (el, zu) => {
try {
await aufruf("bericht", "speichern", {
id: b.id,
titel: el.querySelector("#be-titel").value.trim(),
beschreibung: el.querySelector("#be-text").value,
massnahmen: el.querySelector("#be-mass").value,
grund: (el.querySelector("#be-grund") || {}).value || "",
});
zu(); meldung("Gespeichert.", "gut"); oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
}},
],
});
});
const beteiligt = $("br-beteiligt");
if (beteiligt) beteiligt.addEventListener("click", () => beteiligtDialog(behaelter, b.id));
behaelter.querySelectorAll("[data-bet-weg]").forEach((el) =>
el.addEventListener("click", async () => {
try {
await aufruf("bericht", "beteiligtEntfernen", { id: b.id, index: +el.dataset.betWeg });
oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
}));
const anlage = $("br-anlage");
if (anlage) anlage.addEventListener("click", () => anlageDialog(behaelter, b.id));
behaelter.querySelectorAll("[data-anl-weg]").forEach((el) =>
el.addEventListener("click", async () => {
try {
await aufruf("bericht", "anlageEntfernen", { id: b.id, index: +el.dataset.anlWeg });
oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
}));
const unterschreiben = $("br-unterschreiben");
if (unterschreiben) unterschreiben.addEventListener("click", () => {
bestaetigen({
titel: "Bericht unterschreiben",
inhalt: `<div class="folgen">
<div class="folge">
<span class="folge-symbol">✍</span>
<div>Du bestätigst, dass der Inhalt von <b>${sicher(b.nummer)}</b> aus deiner
Sicht zutrifft. Die Unterschrift ist <b>verbindlich</b> und kann nicht
zurückgenommen werden.</div>
</div>
</div>`,
bestaetigenText: "Unterschreiben",
bestaetigt: async (_, zu) => {
try {
await aufruf("bericht", "unterschreiben", { id: b.id });
zu(); meldung("Unterschrieben.", "gut"); oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const einreichen = $("br-einreichen");
if (einreichen) einreichen.addEventListener("click", () => {
bestaetigen({
titel: "Bericht einreichen",
inhalt: `<p style="margin:0;font-size:13px;line-height:1.6">
<b>${sicher(b.nummer)}</b> geht zur Prüfung an die Führung. Bis zur Freigabe
kannst du ihn weiterhin ändern — jede Änderung nach dem Unterschreiben wird
allerdings vermerkt.</p>`,
bestaetigenText: "Einreichen",
bestaetigt: async (_, zu) => {
try {
await aufruf("bericht", "einreichen", { id: b.id });
zu(); meldung("Eingereicht.", "gut"); oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const freigeben = $("br-freigeben");
if (freigeben) freigeben.addEventListener("click", () => {
bestaetigen({
titel: "Bericht freigeben",
inhalt: `
<div class="folgen">
<div class="folgen-titel">Mit der Freigabe geschieht Folgendes:</div>
<div class="folge">
<span class="folge-symbol">🔒</span>
<div>Der Bericht wird <b>inhaltlich gesperrt</b> — auch der Verfasser kann
ihn danach nicht mehr ändern.</div>
</div>
<div class="folge">
<span class="folge-symbol">✍</span>
<div>Du unterschreibst den Bericht damit zugleich als freigebender Beamter.</div>
</div>
</div>
<div class="feld" style="margin-top:12px"><label>Vermerk</label>
<input id="bf-vermerk" placeholder="optional"></div>`,
bestaetigenText: "Freigeben",
bestaetigt: async (el, zu) => {
try {
await aufruf("bericht", "freigeben", {
id: b.id, vermerk: el.querySelector("#bf-vermerk").value.trim(),
});
zu(); meldung("Freigegeben.", "gut"); oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
const zurueckweisen = $("br-zurueckweisen");
if (zurueckweisen) zurueckweisen.addEventListener("click", () => {
fenster({
titel: "Zur Nachbesserung zurückgeben",
inhalt: `<div class="feld"><label>Was soll nachgebessert werden?</label>
<textarea id="bz-grund" placeholder="Der Verfasser erhält diesen Hinweis."></textarea></div>`,
knoepfe: [
{ text: "Abbrechen", klick: (_, zu) => zu() },
{ text: "Zurückweisen", art: "gefahr", klick: async (el, zu) => {
try {
await aufruf("bericht", "zurueckweisen", {
id: b.id, grund: el.querySelector("#bz-grund").value.trim(),
});
zu(); meldung("Zurückgewiesen."); oeffne(behaelter, b.id);
} catch (f) { meldung(f.message, "fehler"); }
}},
],
});
});
}
/* ---- Dialoge ------------------------------------------------------ */
function anlegenDialog(behaelter) {
aufruf("bericht", "bezuege", {}).then((bez) => {
fenster({
titel: "Bericht anlegen",
inhalt: `
<div class="feld"><label>Titel</label>
<input id="bn-titel" placeholder="Worum geht es?"></div>
<div class="feld"><label>Bezug zu einem Einsatz</label>
<select id="bn-einsatz">
<option value="">— ohne Einsatzbezug —</option>
${(bez.einsaetze || []).map((e) =>
`<option value="${e.id}">${sicher(e.nummer)} · ${sicher(e.titel)}</option>`).join("")}
</select></div>
<div class="feld"><label>Sachverhalt</label>
<textarea id="bn-text" style="min-height:140px"
placeholder="Was ist geschehen? Datum, Uhrzeit, Ablauf."></textarea></div>
<div class="feld"><label>Getroffene Maßnahmen</label>
<textarea id="bn-mass" placeholder="Welche Maßnahmen wurden ergriffen?"></textarea></div>`,
knoepfe: [
{ text: "Abbrechen", klick: (_, zu) => zu() },
{ text: "Anlegen", klick: async (el, zu) => {
try {
const ergebnis = await aufruf("bericht", "erstellen", {
titel: el.querySelector("#bn-titel").value.trim(),
einsatz: +el.querySelector("#bn-einsatz").value || null,
beschreibung: el.querySelector("#bn-text").value,
massnahmen: el.querySelector("#bn-mass").value,
});
zu(); meldung("Bericht " + ergebnis.nummer + " angelegt.", "gut");
oeffne(behaelter, ergebnis.id);
} catch (f) { meldung(f.message, "fehler"); }
}},
],
});
}).catch((f) => meldung(f.message, "fehler"));
}
function beteiligtDialog(behaelter, berichtId) {
fenster({
titel: "Beteiligten hinzufügen",
inhalt: `
<div class="feld"><label>Art</label>
<select id="bb-typ">
<option value="person">Person (Zivilist)</option>
<option value="beamter">Beamter</option>
</select></div>
<div class="feld">
<label>Suchen</label>
<div class="person-waehler">
<input id="bb-suche" placeholder="Name eingeben">
<button class="knopf leise klein" id="bb-suchen">Suchen</button>
</div>
<div class="feld-hinweis">Oder unten einen Namen frei eintragen.</div>
</div>
<div id="bb-treffer"></div>
<div class="feld"><label>Name (frei)</label>
<input id="bb-name" placeholder="Falls die Person nicht im System ist"></div>
<div class="feld"><label>Beteiligung</label>
<input id="bb-rolle" placeholder="z. B. Zeuge, Geschädigter, Beschuldigter"></div>`,
knoepfe: [
{ text: "Abbrechen", klick: (_, zu) => zu() },
{ text: "Hinzufügen", klick: async (el, zu) => {
try {
await aufruf("bericht", "beteiligtHinzu", {
id: berichtId,
typ: el.querySelector("#bb-typ").value,
name: el.querySelector("#bb-name").value.trim(),
rolle: el.querySelector("#bb-rolle").value.trim(),
charakterId: el.dataset.gewaehlt ? +el.dataset.gewaehlt : null,
});
zu(); oeffne(behaelter, berichtId);
} catch (f) { meldung(f.message, "fehler"); }
}},
],
nachOeffnen: (el) => {
const suchen = async () => {
const begriff = el.querySelector("#bb-suche").value.trim();
if (begriff.length < 2) { meldung("Bitte mindestens zwei Zeichen.", "fehler"); return; }
try {
const d = await aufruf("mdt", "personSuchen", { begriff });
const ziel = el.querySelector("#bb-treffer");
ziel.innerHTML = (d.treffer || []).length
? `<div class="liste">${d.treffer.slice(0, 6).map((p) =>
`<div class="liste-zeile treffer" data-wahl="${p.id}"
data-name="${sicher(p.firstname + " " + p.lastname)}">
<div class="lz-haupt"><div class="lz-titel">${sicher(p.firstname + " " + p.lastname)}</div>
<div class="lz-unter">geb. ${sicher(String(p.dob || "").slice(0, 10))}</div></div>
</div>`).join("")}</div>`
: `<div class="liste-leer">Keine Treffer.</div>`;
ziel.querySelectorAll("[data-wahl]").forEach((zeile) =>
zeile.addEventListener("click", () => {
el.dataset.gewaehlt = zeile.dataset.wahl;
el.querySelector("#bb-name").value = zeile.dataset.name;
ziel.innerHTML = `<div class="feld-hinweis">Ausgewählt: <b>${sicher(zeile.dataset.name)}</b></div>`;
}));
} catch (f) { meldung(f.message, "fehler"); }
};
el.querySelector("#bb-suchen").addEventListener("click", suchen);
el.querySelector("#bb-suche").addEventListener("keydown", (e) => {
if (e.key === "Enter") { e.preventDefault(); suchen(); }
});
},
});
}
function anlageDialog(behaelter, berichtId) {
fenster({
titel: "Anlage hinzufügen",
inhalt: `
<div class="feld"><label>Art</label>
<select id="ba-typ">
<option value="datei">Datei / Bild</option>
<option value="beweis">Vorhandenes Beweismittel</option>
</select></div>
<div id="ba-datei">
<div class="feld"><label>Bezeichnung</label>
<input id="ba-titel" placeholder="Was zeigt die Anlage?"></div>
<div class="feld"><label>Adresse</label>
<input id="ba-adresse" placeholder="https://…"></div>
</div>
<div id="ba-beweis" hidden>
<div class="feld">
<label>Beweismittel suchen</label>
<div class="person-waehler">
<input id="ba-suche" placeholder="Kennung oder Bezeichnung">
<button class="knopf leise klein" id="ba-suchen">Suchen</button>
</div>
</div>
<div id="ba-treffer"></div>
</div>`,
knoepfe: [
{ text: "Abbrechen", klick: (_, zu) => zu() },
{ text: "Hinzufügen", klick: async (el, zu) => {
const typ = el.querySelector("#ba-typ").value;
try {
await aufruf("bericht", "anlageHinzu", {
id: berichtId, typ,
titel: (el.querySelector("#ba-titel") || {}).value || "",
adresse: (el.querySelector("#ba-adresse") || {}).value || "",
beweisId: el.dataset.beweis ? +el.dataset.beweis : null,
});
zu(); oeffne(behaelter, berichtId);
} catch (f) { meldung(f.message, "fehler"); }
}},
],
nachOeffnen: (el) => {
el.querySelector("#ba-typ").addEventListener("change", (e) => {
const istBeweis = e.target.value === "beweis";
el.querySelector("#ba-datei").hidden = istBeweis;
el.querySelector("#ba-beweis").hidden = !istBeweis;
});
const suchen = async () => {
const begriff = el.querySelector("#ba-suche").value.trim();
if (begriff.length < 2) { meldung("Bitte mindestens zwei Zeichen.", "fehler"); return; }
try {
const d = await aufruf("bericht", "beweisSuche", { begriff });
const ziel = el.querySelector("#ba-treffer");
ziel.innerHTML = (d.beweise || []).length
? `<div class="liste">${d.beweise.map((b) =>
`<div class="liste-zeile treffer" data-wahl="${b.id}">
<span class="marker neutral">${sicher(b.kennung)}</span>
<div class="lz-haupt"><div class="lz-titel">${sicher(b.bezeichnung)}</div>
<div class="lz-unter">${sicher(b.fundort || "")}</div></div>
</div>`).join("")}</div>`
: `<div class="liste-leer">Keine Treffer.</div>`;
ziel.querySelectorAll("[data-wahl]").forEach((zeile) =>
zeile.addEventListener("click", () => {
el.dataset.beweis = zeile.dataset.wahl;
ziel.innerHTML = `<div class="feld-hinweis">Ausgewählt.</div>`;
}));
} catch (f) { meldung(f.message, "fehler"); }
};
el.querySelector("#ba-suchen").addEventListener("click", suchen);
},
});
}
/* ---- Laden -------------------------------------------------------- */
async function oeffne(behaelter, berichtId) {
try {
const d = await aufruf("bericht", "oeffnen", { id: berichtId });
z.offen = berichtId;
einzelAnsicht(behaelter, d);
} catch (f) { meldung(f.message, "fehler"); }
}
async function rendern(behaelter) {
try {
z.daten = await aufruf("bericht", "liste", {
status: z.filterStatus, nurEigene: z.nurEigene, begriff: z.suche,
});
if (z.offen) await oeffne(behaelter, z.offen);
else uebersicht(behaelter);
} catch (f) {
behaelter.innerHTML = `<div class="seiten-kopf"><div><h2>Berichte</h2>
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
}
}
App.registriereAnsicht("bericht", {
rendern,
aktualisieren: () => {
if (App.zustand.aktivesModul === "bericht") rendern($("inhalt"));
},
});
App.registriereSuche(async (begriff) => {
try {
const d = await aufruf("bericht", "liste", { begriff });
return (d.berichte || []).slice(0, 5).map((b) => ({
bereich: "Bericht", titel: b.titel,
unter: b.nummer + " · " + (statusText[b.status] || b.status),
}));
} catch (e) { return []; }
});
})();