/* ===================================================================== aether-police / Ansicht: Strafenkatalog & Anzeigen Der Katalog liefert einen VORSCHLAG. Die Felder darunter gehören dem Beamten — er kann sie jederzeit ändern. Weicht er ab, wird das sichtbar gemacht und eine Begründung verlangt. ===================================================================== */ "use strict"; (() => { const { sicher, zeit, euro, aufruf, meldung, fenster, bestaetigen, $ } = App; const z = { reiter: "erfassen", // erfassen | vorgaenge | katalog katalog: [], rechte: {}, auswahl: new Set(), // gewählte Delikt-Kennungen vorschlag: null, // { delikte, haft, geld, hinweise } beschuldigter: null, // { id, name } anzeigen: [], filterStatus: "", }; /* ---- Bausteine --------------------------------------------------- */ function reiterLeiste() { const reiter = [ { id: "erfassen", name: "Anzeige erfassen", symbol: "✎" }, { id: "vorgaenge", name: "Vorgänge", symbol: "📋" }, ]; if (z.rechte.bearbeiten) reiter.push({ id: "katalog", name: "Katalog pflegen", symbol: "⚙" }); return `
${reiter.map((r) => ``).join("")}
`; } /** Zeigt Minuten als „1 Std 30 Min" */ function haftText(minuten) { const m = Math.max(0, Math.floor(minuten || 0)); if (m === 0) return "keine Haft"; if (m < 60) return m + " Min"; return Math.floor(m / 60) + " Std " + (m % 60 ? (m % 60) + " Min" : ""); } /* ===================================================================== ANZEIGE ERFASSEN ===================================================================== */ function katalogAuswahl() { if (!z.katalog.length) return `
Der Katalog ist leer.
`; return z.katalog.map((gruppe) => `
${sicher(gruppe.kategorie)}
${gruppe.delikte.map((d) => ` `).join("")}
`).join(""); } function erfassenAnsicht(behaelter) { const v = z.vorschlag; behaelter.innerHTML = `

Anzeige erfassen

Der Katalog schlägt vor — entschieden wird von dir.

${reiterLeiste()}
Strafenkatalog
${katalogAuswahl()}
Vorgang
${z.beschuldigter ? `
Ausgewählt: ${sicher(z.beschuldigter.name)}
` : `
Ohne Beschuldigten kann keine Anzeige erfasst werden.
`}
${v ? `
Vorschlag des Katalogs ${v.delikte.length} Delikt${v.delikte.length === 1 ? "" : "e"}
Haft${haftText(v.haft)}
Geldstrafe${euro(v.geld)}
${v.hinweise.length ? `
${v.hinweise.map((h) => `
ℹ ${sicher(h)}
`).join("")}
` : ""}
Deine Entscheidung
ℹ
Der Katalog ist ein Nachschlagewerk. Beide Werte lassen sich frei anpassen — gespeichert werden Vorschlag und Entscheidung getrennt, damit nachvollziehbar bleibt, wer was festgelegt hat.
Es wird noch nichts eingezogen und niemand kommt in Haft.
` : `
Wähle links mindestens ein Delikt aus.
`}
`; verdrahteErfassen(behaelter); } function verdrahteErfassen(behaelter) { // Delikt an-/abwählen behaelter.querySelectorAll("[data-delikt]").forEach((k) => k.addEventListener("change", async () => { const kennung = k.dataset.delikt; if (k.checked) z.auswahl.add(kennung); else z.auswahl.delete(kennung); await berechneVorschlag(); erfassenAnsicht(behaelter); })); // Katalogsuche (nur Anzeige filtern) const suche = $("an-katalogsuche"); if (suche) suche.addEventListener("input", () => { const begriff = suche.value.trim().toLowerCase(); behaelter.querySelectorAll(".delikt").forEach((el) => { const text = el.textContent.toLowerCase(); el.style.display = !begriff || text.includes(begriff) ? "" : "none"; }); behaelter.querySelectorAll(".katalog-gruppe").forEach((g) => { const sichtbar = [...g.querySelectorAll(".delikt")].some((e) => e.style.display !== "none"); g.style.display = sichtbar ? "" : "none"; }); }); // Person suchen const personSuche = () => { const feld = $("an-person"); const begriff = feld.value.trim(); if (begriff.length < 2) { meldung("Bitte mindestens zwei Zeichen.", "fehler"); return; } personWaehlen(behaelter, begriff); }; const personKnopf = $("an-personsuche"); if (personKnopf) personKnopf.addEventListener("click", personSuche); const personFeld = $("an-person"); if (personFeld) personFeld.addEventListener("keydown", (e) => { if (e.key === "Enter") personSuche(); }); // Abweichung sichtbar machen const pruefeAbweichung = () => { if (!z.vorschlag) return; const haft = +($("an-haft") || {}).value || 0; const geld = +($("an-geld") || {}).value || 0; const abweichung = haft !== z.vorschlag.haft || geld !== z.vorschlag.geld; const block = $("an-abweichung"); if (block) block.hidden = !abweichung; }; ["an-haft", "an-geld"].forEach((id) => { const el = $(id); if (el) el.addEventListener("input", pruefeAbweichung); }); // Erfassen const speichern = $("an-speichern"); if (speichern) speichern.addEventListener("click", async () => { if (!z.beschuldigter) { meldung("Bitte eine Person auswählen.", "fehler"); return; } const haft = +($("an-haft") || {}).value || 0; const geld = +($("an-geld") || {}).value || 0; const abweichung = haft !== z.vorschlag.haft || geld !== z.vorschlag.geld; const grund = ($("an-grund") || {}).value || ""; if (abweichung && !z.rechte.abweichen) { meldung("Dir fehlt die Berechtigung, vom Vorschlag abzuweichen.", "fehler"); return; } if (abweichung && grund.trim().length < 5) { meldung("Bitte begründe die Abweichung.", "fehler"); return; } try { await aufruf("anzeige", "erstellen", { beschuldigter: z.beschuldigter.id, delikte: [...z.auswahl], haft, geld, begruendung: grund, }); meldung("Als Entwurf erfasst — Bestätigung steht noch aus.", "gut"); z.auswahl.clear(); z.vorschlag = null; z.beschuldigter = null; z.reiter = "vorgaenge"; rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }); } /** Holt den Vorschlag zur aktuellen Auswahl vom Server */ async function berechneVorschlag() { if (!z.auswahl.size) { z.vorschlag = null; return; } try { z.vorschlag = await aufruf("anzeige", "vorschlag", { delikte: [...z.auswahl] }); } catch (f) { meldung(f.message, "fehler"); z.vorschlag = null; } } /** Personensuche über das MDT-Modul */ async function personWaehlen(behaelter, begriff) { try { const d = await aufruf("mdt", "personSuchen", { begriff }); const treffer = d.treffer || []; fenster({ titel: "Person auswählen", inhalt: treffer.length ? `
${treffer.map((p) => `
${sicher(p.firstname + " " + p.lastname)}
geb. ${sicher(String(p.dob || "").slice(0, 10))}
`).join("")}
` : `
Keine Treffer.
`, nachOeffnen: (el) => { el.querySelectorAll("[data-wahl]").forEach((zeile) => zeile.addEventListener("click", () => { z.beschuldigter = { id: +zeile.dataset.wahl, name: zeile.dataset.name }; el.remove(); erfassenAnsicht(behaelter); })); }, }); } catch (f) { meldung(f.message, "fehler"); } } /* ===================================================================== VORGÄNGE ===================================================================== */ function vorgaengeAnsicht(behaelter) { const statusArt = { entwurf: "warn", bestaetigt: "info", verworfen: "neutral" }; const statusText = { entwurf: "Entwurf", bestaetigt: "Bestätigt", verworfen: "Verworfen" }; behaelter.innerHTML = `

Vorgänge

Erfasste Anzeigen und ihr Bearbeitungsstand

${reiterLeiste()}
${z.anzeigen.length ? `
${z.anzeigen.map((a) => `
${sicher((a.b_vorname || "") + " " + (a.b_nachname || ""))} ${a.abweichung ? 'abweichend' : ""}
${sicher((a.delikte || []).map((d) => d.name).join(", ") || "—")}
${haftText(a.haft_final)} · ${euro(a.geld_final)} ${a.abweichung ? ` (Vorschlag: ${haftText(a.haft_empfohlen)} · ${euro(a.geld_empfohlen)})` : ""} · erfasst von ${sicher((a.e_vorname || "") + " " + (a.e_nachname || ""))} · ${zeit(a.erstellt_am)}
${a.begruendung ? `
„${sicher(a.begruendung)}"
` : ""}
${sicher(statusText[a.status] || a.status)} ${a.status === "entwurf" && z.rechte.bestaetigen ? `` : ""} ${a.status === "entwurf" ? `` : ""}
`).join("")}
` : `
Keine Vorgänge.
`}
`; $("an-filter").addEventListener("change", (e) => { z.filterStatus = e.target.value; rendern(behaelter); }); behaelter.querySelectorAll("[data-bestaetigen]").forEach((b) => b.addEventListener("click", () => bestaetigenDialog(behaelter, +b.dataset.bestaetigen))); behaelter.querySelectorAll("[data-verwerfen]").forEach((b) => b.addEventListener("click", () => { bestaetigen({ titel: "Entwurf verwerfen", gefahr: true, inhalt: `
`, bestaetigenText: "Verwerfen", bestaetigt: async (el, zu) => { try { await aufruf("anzeige", "verwerfen", { id: +b.dataset.verwerfen, grund: el.querySelector("#av-grund").value.trim(), }); zu(); meldung("Entwurf verworfen."); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); })); } /** Bestätigungsdialog — benennt genau, was ausgeführt wird */ function bestaetigenDialog(behaelter, anzeigeId) { const a = z.anzeigen.find((x) => x.id === anzeigeId); if (!a) return; bestaetigen({ titel: "Anzeige bestätigen", inhalt: `

${sicher((a.b_vorname || "") + " " + (a.b_nachname || ""))}
${sicher((a.delikte || []).map((d) => d.name).join(", "))}

Mit deiner Bestätigung geschieht Folgendes:
💶
Die Geldstrafe von ${euro(a.geld_final)} wird eingezogen und der Staatskasse zugeführt.
🔒
${a.haft_final > 0 ? `Die festgelegte Haftdauer von ${haftText(a.haft_final)} wird vermerkt — eine Einweisung in Gewahrsam erfolgt dadurch nicht. Das ist ein eigener Schritt.` : "Es ist keine Haft vorgesehen."}
📄
Der Vorgang wird mit deinem Namen als bestätigend festgehalten.
${a.abweichung ? `
⚠ Abweichung vom Katalogvorschlag
Vorschlag war ${haftText(a.haft_empfohlen)} · ${euro(a.geld_empfohlen)}. ${a.begruendung ? `Begründung: „${sicher(a.begruendung)}"` : "Keine Begründung hinterlegt."}
` : ""}`, bestaetigenText: "Bestätigen", bestaetigt: async (_, zu) => { try { const ergebnis = await aufruf("anzeige", "bestaetigen", { id: anzeigeId }); zu(); meldung(ergebnis.eingezogen > 0 ? "Bestätigt · " + euro(ergebnis.eingezogen) + " eingezogen." : "Bestätigt.", "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); } /* ===================================================================== KATALOGPFLEGE ===================================================================== */ function katalogAnsicht(behaelter) { behaelter.innerHTML = `

Strafenkatalog pflegen

Die Werte hier sind Empfehlungen — sie binden niemanden.

${reiterLeiste()}
${z.katalog.map((gruppe) => `
${sicher(gruppe.kategorie)}
${gruppe.delikte.map((d) => `
${sicher(d.kennung)}
${sicher(d.bezeichnung)}
${d.paragraph ? sicher(d.paragraph) + " · " : ""} ${haftText(d.haft)} · ${euro(d.geld)} ${d.hinweis ? " · " + sicher(d.hinweis) : ""}
`).join("")}
`).join("")}
`; $("kat-neu").addEventListener("click", () => katalogDialog(behaelter, null)); behaelter.querySelectorAll("[data-kat-bearbeiten]").forEach((b) => b.addEventListener("click", () => { const kennung = b.dataset.katBearbeiten; let eintrag = null; z.katalog.forEach((g) => g.delikte.forEach((d) => { if (d.kennung === kennung) eintrag = d; })); katalogDialog(behaelter, eintrag); })); behaelter.querySelectorAll("[data-kat-weg]").forEach((b) => b.addEventListener("click", () => { bestaetigen({ titel: "Eintrag deaktivieren", inhalt: `

Der Eintrag verschwindet aus der Auswahl, bleibt aber in bestehenden Anzeigen nachvollziehbar. Gelöscht wird nichts.

`, bestaetigenText: "Deaktivieren", bestaetigt: async (_, zu) => { try { await aufruf("anzeige", "katalogDeaktivieren", { kennung: b.dataset.katWeg }); zu(); meldung("Eintrag deaktiviert."); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); })); } function katalogDialog(behaelter, eintrag) { const e = eintrag || {}; fenster({ titel: eintrag ? "Eintrag ändern" : "Neuer Katalogeintrag", inhalt: `
`, knoepfe: [ { text: "Abbrechen", klick: (_, zu) => zu() }, { text: "Speichern", klick: async (el, zu) => { try { await aufruf("anzeige", "katalogSpeichern", { kennung: el.querySelector("#kd-kennung").value.trim(), kategorie: el.querySelector("#kd-kategorie").value.trim(), bezeichnung: el.querySelector("#kd-bezeichnung").value.trim(), paragraph: el.querySelector("#kd-paragraph").value.trim(), haft: +el.querySelector("#kd-haft").value, geld: +el.querySelector("#kd-geld").value, hinweis: el.querySelector("#kd-hinweis").value.trim(), }); zu(); meldung("Gespeichert.", "gut"); rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }}, ], }); } /* ---- Laden -------------------------------------------------------- */ async function rendern(behaelter) { try { const k = await aufruf("anzeige", "katalog", {}); z.katalog = k.katalog; z.rechte = k.rechte; if (z.reiter === "vorgaenge") { const v = await aufruf("anzeige", "liste", { status: z.filterStatus }); z.anzeigen = v.anzeigen; z.rechte = Object.assign({}, z.rechte, v.rechte); vorgaengeAnsicht(behaelter); } else if (z.reiter === "katalog") { katalogAnsicht(behaelter); } else { erfassenAnsicht(behaelter); } behaelter.querySelectorAll("[data-reiter]").forEach((b) => b.addEventListener("click", () => { z.reiter = b.dataset.reiter; rendern(behaelter); })); } catch (f) { behaelter.innerHTML = `

Anzeigen

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

`; } } App.registriereAnsicht("anzeige", { rendern }); })();