/* ===================================================================== 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 = `

Berichte

Entwurf → Eingereicht → Freigegeben. Jede Stufe verlangt eine Entscheidung.

${d.rechte.erstellen ? `` : ""}
Meine offenen Entwürfe
${d.eigeneOffen}
warten auf Ausarbeitung
Zur Prüfung eingereicht
${d.zurPruefung}
${d.rechte.freigeben ? "warten auf deine Freigabe" : "in Bearbeitung"}
${d.berichte.length ? `
${d.berichte.map((b) => `
${sicher(b.titel)} ${b.eigener ? 'eigener' : ""}
${sicher(b.nummer)} ${b.einsatz_nummer ? " · Einsatz " + sicher(b.einsatz_nummer) : ""} · ${sicher(b.dienstnummer || "")} ${sicher((b.firstname || "") + " " + (b.lastname || ""))} · ${zeit(b.erstellt_am)}
${sicher(statusText[b.status] || b.status)} ${b.unterschriften.length ? `✍ ${b.unterschriften.length}` : ""}
`).join("")}
` : `
Keine Berichte gefunden.
`}
`; 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 = `

${sicher(b.titel)}

${sicher(b.nummer)} ${b.einsatz_nummer ? " · Einsatz " + sicher(b.einsatz_nummer) : ""} ${b.aktenzeichen ? " · Akte " + sicher(b.aktenzeichen) : ""}

${bearbeitbar ? `` : ""} ${!d.habeUnterschrieben && b.status !== "freigegeben" ? `` : ""} ${b.status === "entwurf" && bearbeitbar ? `` : ""} ${b.status === "eingereicht" && d.rechte.freigeben ? ` ` : ""}
${b.status === "freigegeben" ? `
🔒 Dieser Bericht ist freigegeben und damit inhaltlich gesperrt — auch für den Verfasser.
` : ""}
Sachverhalt ${sicher(statusText[b.status] || b.status)}
Verfasser${sicher(b.dienstnummer || "")} ${sicher((b.firstname || "") + " " + (b.lastname || ""))} Angelegt${zeit(b.erstellt_am)}
${b.beschreibung ? sicher(b.beschreibung).replace(/\n/g, "
") : "Noch keine Beschreibung erfasst."}
Getroffene Maßnahmen
${b.massnahmen ? sicher(b.massnahmen).replace(/\n/g, "
") : "Keine Maßnahmen dokumentiert."}
Beteiligte ${bearbeitbar ? `` : ""}
${b.beteiligte.length ? `
${b.beteiligte.map((p, i) => `
${p.typ === "beamter" ? (p.dienstnummer || "Beamter") : "Person"}
${sicher(p.name)}
${p.rolle ? `
${sicher(p.rolle)}
` : ""}
${bearbeitbar && p.charakterId !== b.erstellt_von ? `` : ""}
`).join("")}
` : `
Keine Beteiligten erfasst.
`}
Anlagen & Beweise ${bearbeitbar ? `` : ""}
${b.anlagen.length ? `
${b.anlagen.map((a, i) => `
${a.typ === "beweis" ? sicher(a.kennung) : "Anlage"}
${sicher(a.titel)}
${a.adresse ? `
${sicher(a.adresse)}
` : ""}
${a.adresse ? `Öffnen` : ""} ${bearbeitbar ? `` : ""}
`).join("")}
` : `
Keine Anlagen.
`}
Unterschriften ${b.unterschriften.length}
${b.unterschriften.length ? `
${b.unterschriften.map((u) => `
${sicher(u.name)}
${sicher(u.dienstnummer || "")} · ${sicher(u.rolle || "")}
${zeit(u.am)}${u.freigabe ? " · Freigabe" : ""}
`).join("")}
` : `
Noch nicht unterschrieben.
`}
ℹ
Eine Unterschrift ist verbindlich und wird nicht zurückgenommen. Sie bestätigt, dass der Inhalt aus deiner Sicht zutrifft.
`; 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: `
${unterschrieben ? `
⚠ Der Bericht wurde bereits unterschrieben
` : ""}`, 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: `
✍
Du bestätigst, dass der Inhalt von ${sicher(b.nummer)} aus deiner Sicht zutrifft. Die Unterschrift ist verbindlich und kann nicht zurückgenommen werden.
`, 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: `

${sicher(b.nummer)} geht zur Prüfung an die Führung. Bis zur Freigabe kannst du ihn weiterhin ändern — jede Änderung nach dem Unterschreiben wird allerdings vermerkt.

`, 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: `
Mit der Freigabe geschieht Folgendes:
🔒
Der Bericht wird inhaltlich gesperrt — auch der Verfasser kann ihn danach nicht mehr ändern.
✍
Du unterschreibst den Bericht damit zugleich als freigebender Beamter.
`, 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: `
`, 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: `
`, 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: `
Oder unten einen Namen frei eintragen.
`, 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 ? `
${d.treffer.slice(0, 6).map((p) => `
${sicher(p.firstname + " " + p.lastname)}
geb. ${sicher(String(p.dob || "").slice(0, 10))}
`).join("")}
` : `
Keine Treffer.
`; 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 = `
Ausgewählt: ${sicher(zeile.dataset.name)}
`; })); } 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: `
`, 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 ? `
${d.beweise.map((b) => `
${sicher(b.kennung)}
${sicher(b.bezeichnung)}
${sicher(b.fundort || "")}
`).join("")}
` : `
Keine Treffer.
`; ziel.querySelectorAll("[data-wahl]").forEach((zeile) => zeile.addEventListener("click", () => { el.dataset.beweis = zeile.dataset.wahl; ziel.innerHTML = `
Ausgewählt.
`; })); } 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 = `

Berichte

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

`; } } 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 []; } }); })();