/* =====================================================================
aether-police / Ansicht: Administration
Module an- und abschalten, Strafenkatalog pflegen, Protokoll lesen.
Die Reiter richten sich nach den Rechten des Beamten — wer nur das
Protokoll lesen darf, sieht auch nur dieses.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, euro, aufruf, meldung, fenster, bestaetigen } = App;
const z = {
bereiche: null, // welche Reiter erlaubt sind
reiter: null,
daten: null,
katalogSuche: "",
katalogKategorie: "",
katalogStillgelegt: false,
protokollModul: "",
protokollNummer: "",
};
/* ---- Reiter ---------------------------------------------------------- */
function reiterleiste() {
const moeglich = [
{ id: "module", text: "Module", erlaubt: z.bereiche.module },
{ id: "katalog", text: "Strafenkatalog", erlaubt: z.bereiche.katalog },
{ id: "protokoll", text: "Protokoll", erlaubt: z.bereiche.protokoll },
].filter((e) => e.erlaubt);
return `
${moeglich.map((e) => `
${sicher(e.text)} `).join("")}
`;
}
function verdrahteReiter(behaelter) {
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
el.addEventListener("click", () => {
z.reiter = el.dataset.reiter;
rendern(behaelter);
});
});
}
/* ---- Module ----------------------------------------------------------- */
function moduleAnsicht(behaelter) {
const d = z.daten;
const aktiv = d.module.filter((m) => m.aktiv).length;
behaelter.innerHTML = `
Module
Jeder Baustein lässt sich einzeln abschalten. Abgeschaltete Module
verschwinden aus der Seitenleiste — ihre Daten bleiben unangetastet.
${reiterleiste()}
${aktiv}/${d.module.length}
Module aktiv
${d.beamteImDienst}
Beamte im Dienst
${d.module.reduce((s, m) => s + m.aktionen, 0)}
registrierte Aktionen
Registrierte Module
${d.module.map(modulZeile).join("")}
Konfiguration
Steht in config.lua — Änderung erfordert Neustart
${d.konfiguration.map((k) =>
`
${sicher(k.name)} ${sicher(k.wert)}
`).join("")}
`;
verdrahteModule(behaelter);
}
function modulZeile(m) {
return `
${sicher(m.icon)}
${sicher(m.name)}
${sicher(m.beschreibung || "")}
${m.rechtName ? ` · Recht: ${sicher(m.rechtName)}`
: m.recht ? ` · Recht: ${sicher(String(m.recht))}` : " · für alle sichtbar"}
${m.aktionen} Aktionen
${m.unverzichtbar
? `
unverzichtbar `
: `
${m.aktiv ? "Abschalten" : "Einschalten"} `}
`;
}
function verdrahteModule(behaelter) {
verdrahteReiter(behaelter);
behaelter.querySelectorAll("[data-schalten]").forEach((el) => {
el.addEventListener("click", () => {
const id = el.dataset.schalten;
const einschalten = el.dataset.wert === "1";
const modul = z.daten.module.find((m) => m.id === id);
bestaetigen({
titel: einschalten ? "Modul einschalten" : "Modul abschalten",
gefahr: !einschalten,
inhalt: `${sicher(modul.name)} wird
${einschalten ? "wieder in der Seitenleiste erscheinen"
: "für alle Beamten aus der Seitenleiste verschwinden"}.
${einschalten ? "" : `Vorhandene Daten bleiben erhalten
und sind nach dem Wiedereinschalten unverändert da. Beamte, die das Tablet
gerade offen haben, sehen die Änderung beim nächsten Öffnen.
`}
Grund (freiwillig)
`,
bestaetigenText: einschalten ? "Einschalten" : "Abschalten",
bestaetigt: async (dialog, zu) => {
try {
await aufruf("admin", "modulSchalten", {
id, aktiv: einschalten,
grund: dialog.querySelector("[name=grund]").value.trim(),
});
zu();
meldung(einschalten ? "Modul eingeschaltet." : "Modul abgeschaltet.", "gut");
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
});
}
/* ---- Strafenkatalog ---------------------------------------------------- */
function katalogAnsicht(behaelter) {
const d = z.daten;
// Delikte nach Kategorie gruppieren
const gruppen = {};
d.delikte.forEach((delikt) => {
(gruppen[delikt.kategorie] = gruppen[delikt.kategorie] || []).push(delikt);
});
behaelter.innerHTML = `
Strafenkatalog
Die Werte hier sind Vorschläge . Sie werden nie automatisch verhängt —
ein Beamter kann jederzeit begründet davon abweichen.
+ Delikt anlegen
${reiterleiste()}
Alle Kategorien
${d.kategorien.map((k) =>
`
${sicher(k.kategorie)} `).join("")}
${z.katalogStillgelegt ? "Auch stillgelegte" : "Nur aktive"}
${Object.keys(gruppen).length
? Object.keys(gruppen).map((kategorie) => `
${sicher(kategorie)}
${gruppen[kategorie].length} Delikte
${gruppen[kategorie].map(deliktZeile).join("")}
`).join("")
: `Kein Delikt gefunden.
`}
`;
verdrahteKatalog(behaelter);
}
function deliktZeile(delikt) {
return `
${sicher(delikt.kennung)}
${sicher(delikt.bezeichnung)}
${sicher(delikt.paragraph || "ohne Paragraf")}
${delikt.hinweis ? " · " + sicher(delikt.hinweis) : ""}
${delikt.haft || 0} Min
${euro(delikt.geld || 0)}
${delikt.aktiv ? "" : `
stillgelegt `}
Bearbeiten
${delikt.aktiv ? "Stilllegen" : "Freigeben"}
`;
}
function verdrahteKatalog(behaelter) {
verdrahteReiter(behaelter);
const suchfeld = behaelter.querySelector("[data-suche]");
let warten = null;
suchfeld.addEventListener("input", () => {
clearTimeout(warten);
warten = setTimeout(() => {
z.katalogSuche = suchfeld.value.trim();
rendern(behaelter);
}, 260);
});
behaelter.querySelector("[data-kategorie]").addEventListener("change", (e) => {
z.katalogKategorie = e.target.value;
rendern(behaelter);
});
behaelter.querySelector("[data-stillgelegt]").addEventListener("click", () => {
z.katalogStillgelegt = !z.katalogStillgelegt;
rendern(behaelter);
});
behaelter.querySelector("[data-neu]").addEventListener("click", () =>
deliktFenster(behaelter, null));
behaelter.querySelectorAll("[data-bearbeiten]").forEach((el) => {
el.addEventListener("click", () => {
const delikt = z.daten.delikte.find((d) => d.id === +el.dataset.bearbeiten);
deliktFenster(behaelter, delikt);
});
});
behaelter.querySelectorAll("[data-stilllegen]").forEach((el) => {
el.addEventListener("click", () => {
const delikt = z.daten.delikte.find((d) => d.id === +el.dataset.stilllegen);
const freigeben = el.dataset.wert === "1";
bestaetigen({
titel: freigeben ? "Delikt freigeben" : "Delikt stilllegen",
gefahr: !freigeben,
inhalt: `${sicher(delikt.kennung)} — ${sicher(delikt.bezeichnung)}
${freigeben
? "Das Delikt steht wieder zur Auswahl."
: "Das Delikt lässt sich für neue Anzeigen nicht mehr auswählen."}
Gelöscht wird nichts — bestehende Anzeigen behalten
ihren Eintrag und bleiben nachvollziehbar.
Grund (freiwillig)
`,
bestaetigenText: freigeben ? "Freigeben" : "Stilllegen",
bestaetigt: async (dialog, zu) => {
try {
await aufruf("admin", "katalogSchalten", {
id: delikt.id, aktiv: freigeben,
grund: dialog.querySelector("[name=grund]").value.trim(),
});
zu();
meldung(freigeben ? "Delikt freigegeben." : "Delikt stillgelegt.", "gut");
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
});
}
function deliktFenster(behaelter, delikt) {
const neu = !delikt;
delikt = delikt || { kennung: "", kategorie: "", bezeichnung: "", paragraph: "",
haft: 0, geld: 0, hinweis: "", sortierung: 0 };
fenster({
titel: neu ? "Delikt anlegen" : "Delikt bearbeiten",
inhalt: `
Bezeichnung
Paragraf
Hinweis für den Beamten
Sortierung
ℹ
Diese Werte sind Vorschläge . Das System verhängt sie nie —
es rechnet sie nur zusammen und legt sie dem Beamten vor.
`,
knoepfe: [
{ text: "Abbrechen", art: "leise", klick: (_, zu) => zu() },
{ text: neu ? "Anlegen" : "Speichern", klick: async (el, zu) => {
const feld = (name) => el.querySelector(`[name=${name}]`).value.trim();
try {
await aufruf("admin", "katalogSpeichern", {
id: neu ? null : delikt.id,
kennung: feld("kennung"),
kategorie: feld("kategorie"),
bezeichnung: feld("bezeichnung"),
paragraph: feld("paragraph"),
haft: +feld("haft"),
geld: +feld("geld"),
hinweis: feld("hinweis"),
sortierung: +feld("sortierung"),
});
zu();
meldung(neu ? "Delikt angelegt." : "Delikt gespeichert.", "gut");
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
} },
],
});
}
/* ---- Protokoll ---------------------------------------------------------- */
function protokollAnsicht(behaelter) {
const d = z.daten;
behaelter.innerHTML = `
Protokoll
Jede Entscheidung im System steht hier mit Beamtem, Zeit und Begründung.
Einträge lassen sich weder ändern noch entfernen.
${d.gesamt.toLocaleString("de-DE")} Einträge gesamt
${reiterleiste()}
Alle Module
${d.module.map((m) =>
`
${sicher(m.modul)} `).join("")}
Filter zurücksetzen
${d.eintraege.length
? d.eintraege.map(protokollZeile).join("")
: `
${d.unbekannteNummer
? "Diese Dienstnummer gibt es nicht."
: "Kein Eintrag gefunden."}
`}
`;
verdrahteProtokoll(behaelter);
}
function protokollZeile(e) {
// Details als kurze Schlüssel-Wert-Kette
const details = e.details && typeof e.details === "object"
? Object.keys(e.details).map((k) =>
`${sicher(k)}: ${sicher(String(e.details[k]))}`).join(" · ")
: "";
return `
${sicher(e.modul)}
${sicher(e.aktion)}
${sicher(e.name)}${e.dienstnummer ? " (" + sicher(e.dienstnummer) + ")" : ""}
${e.ziel_typ ? ` · ${sicher(e.ziel_typ)} ${sicher(e.ziel_id || "")}` : ""}
${details ? ` · ${details}` : ""}
${e.grund ? `
„${sicher(e.grund)}"
` : ""}
${zeit(e.am || e.erstellt_am)}
`;
}
function verdrahteProtokoll(behaelter) {
verdrahteReiter(behaelter);
behaelter.querySelector("[data-modul]").addEventListener("change", (e) => {
z.protokollModul = e.target.value;
rendern(behaelter);
});
const nummer = behaelter.querySelector("[data-nummer]");
let warten = null;
nummer.addEventListener("input", () => {
clearTimeout(warten);
warten = setTimeout(() => {
z.protokollNummer = nummer.value.trim();
rendern(behaelter);
}, 320);
});
behaelter.querySelector("[data-zuruecksetzen]").addEventListener("click", () => {
z.protokollModul = "";
z.protokollNummer = "";
rendern(behaelter);
});
}
/* ---- Laden -------------------------------------------------------------- */
async function rendern(behaelter) {
try {
// Erlaubte Bereiche einmal holen und den Startreiter danach wählen
if (!z.bereiche) {
z.bereiche = await aufruf("admin", "bereiche", {});
z.reiter = z.bereiche.module ? "module"
: z.bereiche.katalog ? "katalog"
: z.bereiche.protokoll ? "protokoll" : null;
}
if (!z.reiter) {
behaelter.innerHTML = `Administration
Für keinen Bereich der Administration hast du eine Berechtigung.
`;
return;
}
if (z.reiter === "katalog") {
z.daten = await aufruf("admin", "katalog", {
begriff: z.katalogSuche, kategorie: z.katalogKategorie,
stillgelegt: z.katalogStillgelegt,
});
katalogAnsicht(behaelter);
} else if (z.reiter === "protokoll") {
z.daten = await aufruf("admin", "protokoll", {
modul: z.protokollModul, dienstnummer: z.protokollNummer, limit: 200,
});
protokollAnsicht(behaelter);
} else {
z.daten = await aufruf("admin", "module", {});
moduleAnsicht(behaelter);
}
} catch (f) {
behaelter.innerHTML = `Administration
Konnte nicht geladen werden: ${sicher(f.message)}
`;
}
}
App.registriereAnsicht("admin", { rendern });
})();