/* ===================================================================== aether-police / Ansicht: Gewahrsamsverwaltung Zeigt Zellenbelegung und laufende Fristen. Abgelaufene Fristen werden deutlich hervorgehoben — aber niemand wird dadurch entlassen. Die Freilassung ist immer eine bewusste Handlung eines Beamten. ===================================================================== */ "use strict"; (() => { const { sicher, zeit, aufruf, meldung, fenster, bestaetigen, $ } = App; const z = { daten: null, reiter: "aktuell", // aktuell | verlauf }; /* Laufender Ticker, damit die Restzeiten mitlaufen */ let ticker = null; /* ---- Bausteine --------------------------------------------------- */ /** Sekunden als „12:34" oder „abgelaufen" */ function restText(sekunden) { if (sekunden <= 0) return "abgelaufen"; const m = Math.floor(sekunden / 60); const s = Math.floor(sekunden % 60); return m + ":" + String(s).padStart(2, "0") + " Min"; } function reiterLeiste() { return `
`; } function zellenUebersicht(zellen) { return `
${zellen.map((zelle) => `
${sicher(zelle.name)}
${zelle.frei ? `
frei
` : `
${sicher(zelle.insasse.name)}
${restText(zelle.insasse.rest)}
`}
`).join("")}
`; } function bestandListe(offene) { if (!offene.length) return `
Niemand in Gewahrsam.
`; return `
${offene.map((g) => `
${sicher(g.firstname + " " + g.lastname)} ${g.zelle ? `${sicher(g.zelle)}` : ""} ${g.online ? "" : 'offline'}
${sicher(g.grund)}
${g.haft_minuten} Min festgelegt · eingewiesen von ${sicher(g.e_dienstnummer || "")} ${sicher((g.e_vorname || "") + " " + (g.e_nachname || ""))} · ${zeit(g.beginn)}
${restText(g.rest_sekunden)}
${z.daten.rechte.entlassen ? `` : ""} ${z.daten.rechte.einweisen ? `` : ""} ${z.daten.rechte.entlassen ? `` : ""}
`).join("")}
`; } /* ---- Aktuelle Ansicht -------------------------------------------- */ function aktuellAnsicht(behaelter) { const d = z.daten; const abgelaufen = d.offene.filter((g) => g.abgelaufen).length; behaelter.innerHTML = `

Gewahrsam

Fristen laufen mit — entlassen wird ausschließlich durch einen Beamten.

${d.rechte.einweisen ? `` : ""}
${reiterLeiste()} ${abgelaufen > 0 ? `
⏰ ${abgelaufen} Haftzeit${abgelaufen === 1 ? "" : "en"} abgelaufen. Die Person${abgelaufen === 1 ? " bleibt" : "en bleiben"} im Gewahrsam, bis ein Beamter über die Entlassung entscheidet.
` : ""}
Zellenbelegung ${d.zellen.filter((c) => !c.frei).length} / ${d.zellen.length}
${zellenUebersicht(d.zellen)}
Aktueller Bestand ${d.offene.length}
${bestandListe(d.offene)}
ℹ
Das System erinnert an ablaufende Fristen — es entlässt niemanden. Auch eine längst abgelaufene Haftzeit bleibt offen stehen, bis jemand entscheidet.
`; verdrahteAktuell(behaelter); starteTicker(behaelter); } function verdrahteAktuell(behaelter) { const neu = $("gw-neu"); if (neu) neu.addEventListener("click", () => einweisenDialog(behaelter)); behaelter.querySelectorAll("[data-entlassen]").forEach((b) => b.addEventListener("click", () => entlassenDialog(behaelter, +b.dataset.entlassen))); behaelter.querySelectorAll("[data-frist]").forEach((b) => b.addEventListener("click", () => fristDialog(behaelter, +b.dataset.frist))); behaelter.querySelectorAll("[data-zelle]").forEach((b) => b.addEventListener("click", () => zelleDialog(behaelter, +b.dataset.zelle))); } /** Lässt die Restzeiten sekündlich weiterlaufen, ohne neu zu laden */ function starteTicker(behaelter) { clearInterval(ticker); ticker = setInterval(() => { const felder = behaelter.querySelectorAll("[data-rest]"); if (!felder.length) { clearInterval(ticker); return; } felder.forEach((el) => { let rest = +el.dataset.rest - 1; el.dataset.rest = rest; el.textContent = restText(rest); if (rest <= 0) el.classList.add("abgelaufen"); }); }, 1000); } /* ---- Einweisen ---------------------------------------------------- */ function einweisenDialog(behaelter) { const freieZellen = z.daten.zellen.filter((c) => c.frei); let gewaehlt = null; // { id, name } const f = fenster({ titel: "In Gewahrsam nehmen", inhalt: `
Noch niemand ausgewählt.
ℹ
Die Haftdauer legst du fest. Ein Katalogvorschlag aus einer Anzeige ist nur eine Empfehlung und wird hier nicht automatisch übernommen. ${z.daten.verbringen ? "

Die Person wird in die gewählte Zelle verbracht." : "

Die Person wird nicht automatisch verbracht — das geschieht im Rollenspiel."}
`, knoepfe: [ { text: "Abbrechen", klick: (_, zu) => zu() }, { text: "In Gewahrsam nehmen", klick: async (el, zu) => { if (!gewaehlt) { meldung("Bitte eine Person auswählen.", "fehler"); return; } try { await aufruf("gewahrsam", "einweisen", { charakterId: gewaehlt.id, dauer: +el.querySelector("#gw-dauer").value, zelle: el.querySelector("#gw-zelle").value || null, grund: el.querySelector("#gw-grund").value.trim(), }); zu(); meldung("In Gewahrsam genommen.", "gut"); rendern(behaelter); } catch (fehler) { meldung(fehler.message, "fehler"); } }}, ], nachOeffnen: (el) => { const suchen = async () => { const begriff = el.querySelector("#gw-person").value.trim(); if (begriff.length < 2) { meldung("Bitte mindestens zwei Zeichen.", "fehler"); return; } try { const d = await aufruf("mdt", "personSuchen", { begriff }); const treffer = d.treffer || []; if (!treffer.length) { meldung("Keine Treffer."); return; } // Ergebnisse direkt unter dem Feld anbieten const anzeige = el.querySelector("#gw-gewaehlt"); anzeige.innerHTML = `
${treffer.slice(0, 6).map((p) => `
${sicher(p.firstname + " " + p.lastname)}
geb. ${sicher(String(p.dob || "").slice(0, 10))}
`).join("")}
`; anzeige.querySelectorAll("[data-wahl]").forEach((zeile) => zeile.addEventListener("click", () => { gewaehlt = { id: +zeile.dataset.wahl, name: zeile.dataset.name }; anzeige.innerHTML = `Ausgewählt: ${sicher(gewaehlt.name)}`; })); } catch (fehler) { meldung(fehler.message, "fehler"); } }; el.querySelector("#gw-suchen").addEventListener("click", suchen); el.querySelector("#gw-person").addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); suchen(); } }); }, }); return f; } /* ---- Entlassen ---------------------------------------------------- */ function entlassenDialog(behaelter, gewahrsamId) { const g = z.daten.offene.find((x) => x.id === gewahrsamId); if (!g) return; const vorzeitig = g.rest_sekunden > 0; const restMinuten = Math.ceil(g.rest_sekunden / 60); bestaetigen({ titel: vorzeitig ? "Vorzeitig entlassen" : "Entlassen", gefahr: vorzeitig, inhalt: `

${sicher(g.firstname + " " + g.lastname)}
${sicher(g.grund)}

${vorzeitig ? `
⚠ Die Frist läuft noch ${restMinuten} Minuten
Eine vorzeitige Entlassung ist möglich — sie verlangt aber eine Begründung und wird als solche protokolliert.
` : `
⏰
Die festgelegte Haftzeit ist abgelaufen. Das System hat lediglich daran erinnert — die Entlassung führst du aus.
`}
`, bestaetigenText: "Entlassung ausführen", bestaetigt: async (el, zu) => { try { await aufruf("gewahrsam", "entlassen", { id: gewahrsamId, grund: el.querySelector("#ge-grund").value.trim(), }); zu(); meldung("Entlassung durchgeführt.", "gut"); rendern(behaelter); } catch (fehler) { meldung(fehler.message, "fehler"); } }, }); } /* ---- Frist anpassen ----------------------------------------------- */ function fristDialog(behaelter, gewahrsamId) { fenster({ titel: "Haftzeit anpassen", inhalt: `
Beispiel: -10 verkürzt um zehn Minuten, 15 verlängert um fünfzehn.
`, knoepfe: [ { text: "Abbrechen", klick: (_, zu) => zu() }, { text: "Frist ändern", klick: async (el, zu) => { try { await aufruf("gewahrsam", "fristAendern", { id: gewahrsamId, minuten: +el.querySelector("#gf-minuten").value, grund: el.querySelector("#gf-grund").value.trim(), }); zu(); meldung("Frist angepasst.", "gut"); rendern(behaelter); } catch (fehler) { meldung(fehler.message, "fehler"); } }}, ], }); } /* ---- Zelle wechseln ----------------------------------------------- */ function zelleDialog(behaelter, gewahrsamId) { const frei = z.daten.zellen.filter((c) => c.frei); if (!frei.length) { meldung("Keine freie Zelle verfügbar.", "fehler"); return; } fenster({ titel: "Zelle wechseln", inhalt: `
`, knoepfe: [ { text: "Abbrechen", klick: (_, zu) => zu() }, { text: "Verlegen", klick: async (el, zu) => { try { await aufruf("gewahrsam", "zelleWechseln", { id: gewahrsamId, zelle: el.querySelector("#gz-zelle").value, }); zu(); meldung("Verlegt.", "gut"); rendern(behaelter); } catch (fehler) { meldung(fehler.message, "fehler"); } }}, ], }); } /* ---- Verlauf ------------------------------------------------------ */ function verlaufAnsicht(behaelter) { const v = z.daten.verlauf; behaelter.innerHTML = `

Gewahrsam — Verlauf

Abgeschlossene Vorgänge, nachvollziehbar mit entscheidendem Beamten

${reiterLeiste()}
${v.length ? `
${v.map((g) => `
${sicher(g.firstname + " " + g.lastname)}
${sicher(g.grund)}
${g.haft_minuten} Min · ${zeit(g.beginn)} bis ${zeit(g.entlassen_am)} · entlassen durch ${sicher((g.e_vorname || "") + " " + (g.e_nachname || ""))} ${g.entlass_grund ? ` · „${sicher(g.entlass_grund)}"` : ""}
`).join("")}
` : `
Keine abgeschlossenen Vorgänge.
`}
`; behaelter.querySelectorAll("[data-reiter]").forEach((b) => b.addEventListener("click", () => { z.reiter = b.dataset.reiter; rendern(behaelter); })); } /* ---- Laden -------------------------------------------------------- */ async function rendern(behaelter) { try { z.daten = await aufruf("gewahrsam", "liste", {}); if (z.reiter === "verlauf") verlaufAnsicht(behaelter); else aktuellAnsicht(behaelter); behaelter.querySelectorAll("[data-reiter]").forEach((b) => b.addEventListener("click", () => { z.reiter = b.dataset.reiter; rendern(behaelter); })); } catch (fehler) { behaelter.innerHTML = `

Gewahrsam

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

`; } } App.registriereAnsicht("gewahrsam", { rendern, aktualisieren: () => { if (App.zustand.aktivesModul === "gewahrsam") rendern($("inhalt")); }, }); })();