aether-framework/resources/[aether]/aether-police/server/module/dashboard.lua
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

171 lines
6.3 KiB
Lua

-- =====================================================================
-- aether-police / Modul: Dashboard
--
-- Zeigt eine Lageübersicht. Alle Zahlen werden bei jedem Aufruf frisch
-- aus der Datenbank bzw. dem Dienstsystem geholt — nichts ist fest
-- programmiert. Das Dashboard bewertet nichts, es zeigt nur an.
-- =====================================================================
Kern.RegistriereModul({
id = 'dashboard', name = 'Übersicht', icon = '▤',
reihenfolge = 1,
beschreibung = 'Lageübersicht der Dienststelle',
})
--- Sammelt alle Kennzahlen für die Übersicht
Kern.RegistriereAktion('dashboard', 'laden', nil, function(quelle, beamter)
local charakterId = beamter.charakterId
-- --- Beamte im Dienst ---
local dienstliste = Dienst.BaueListe()
local nachStatus = {}
for _, eintrag in ipairs(dienstliste) do
nachStatus[eintrag.status] = (nachStatus[eintrag.status] or 0) + 1
end
-- --- Offene Einsätze ---
local einsaetze = MySQL.query.await([[
SELECT id, nummer, titel, kategorie, prioritaet, ort, status, erstellt_am,
pos_x, pos_y, pos_z
FROM police_einsatz
WHERE status <> 'abgeschlossen'
ORDER BY prioritaet ASC, id DESC
LIMIT 20
]]) or {}
-- --- Offene Anzeigen (Entwürfe warten auf Bestätigung) ---
local anzeigenOffen = MySQL.scalar.await(
"SELECT COUNT(*) FROM police_anzeige WHERE status = 'entwurf'") or 0
-- --- Aktive Fahndungen ---
local fahndungen = MySQL.query.await([[
SELECT id, typ, bezeichnung, grund, gefahr, prioritaet
FROM police_fahndung
WHERE aktiv = 1
ORDER BY prioritaet ASC, id DESC
LIMIT 10
]]) or {}
-- --- Gefangene und auslaufende Haftzeiten ---
local gewahrsam = MySQL.query.await([[
SELECT g.id, g.character, g.grund, g.faellig_am, g.zelle,
TIMESTAMPDIFF(MINUTE, NOW(), g.faellig_am) AS rest,
c.firstname, c.lastname
FROM police_gewahrsam g
JOIN aether_characters c ON c.id = g.character
WHERE g.entlassen_am IS NULL
ORDER BY g.faellig_am ASC
]]) or {}
local abgelaufen = 0
for _, eintrag in ipairs(gewahrsam) do
if (tonumber(eintrag.rest) or 0) <= 0 then abgelaufen = abgelaufen + 1 end
end
-- --- Offene Berichte des eigenen Beamten ---
local eigeneBerichte = MySQL.query.await([[
SELECT id, nummer, titel, status, erstellt_am
FROM police_bericht
WHERE erstellt_von = ? AND status = 'entwurf'
ORDER BY id DESC LIMIT 10
]], { charakterId }) or {}
-- --- Akten, die auf Freigabe warten ---
local akteFreigabe = MySQL.scalar.await(
"SELECT COUNT(*) FROM police_akte WHERE status = 'pruefung'") or 0
-- --- Dienstfahrzeuge im Einsatz ---
local fahrzeuge = MySQL.query.await([[
SELECT id, kennzeichen, bezeichnung, status, wartung, tank, zugewiesen_an
FROM police_fahrzeug
ORDER BY kennzeichen ASC
]]) or {}
local fahrzeugeImEinsatz = 0
local wartungFaellig = 0
for _, fahrzeug in ipairs(fahrzeuge) do
if fahrzeug.status ~= 'verfuegbar' then fahrzeugeImEinsatz = fahrzeugeImEinsatz + 1 end
if fahrzeug.wartung ~= 'ok' then wartungFaellig = wartungFaellig + 1 end
end
-- --- Neueste Mitteilungen ---
local mitteilungen = MySQL.query.await([[
SELECT n.id, n.betreff, n.text, n.gesendet, c.firstname, c.lastname
FROM police_nachricht n
LEFT JOIN aether_characters c ON c.id = n.absender
WHERE n.empf_typ = 'rundmeldung'
OR (n.empf_typ = 'person' AND n.empfaenger = ?)
OR (n.empf_typ = 'abteilung' AND n.empfaenger = ?)
ORDER BY n.id DESC LIMIT 5
]], { tostring(charakterId), beamter.abteilung }) or {}
-- --- Warnungen zusammenstellen (reine Hinweise) ---
local warnungen = {}
if abgelaufen > 0 then
warnungen[#warnungen + 1] = {
prioritaet = 'hoch',
text = ('%d Haftzeit%s abgelaufen — Entlassung muss manuell entschieden werden.')
:format(abgelaufen, abgelaufen == 1 and '' or 'en'),
zielTyp = 'gewahrsam',
}
end
if anzeigenOffen > 0 then
warnungen[#warnungen + 1] = {
prioritaet = 'normal',
text = ('%d Anzeige%s im Entwurf — wartet auf Bestätigung.')
:format(anzeigenOffen, anzeigenOffen == 1 and '' or 'n'),
zielTyp = 'anzeigen',
}
end
if akteFreigabe > 0 then
warnungen[#warnungen + 1] = {
prioritaet = 'normal',
text = ('%d Akte%s wartet auf Freigabe.')
:format(akteFreigabe, akteFreigabe == 1 and '' or 'n'),
zielTyp = 'akten',
}
end
if wartungFaellig > 0 then
warnungen[#warnungen + 1] = {
prioritaet = 'niedrig',
text = ('%d Dienstfahrzeug%s benötigt Wartung.')
:format(wartungFaellig, wartungFaellig == 1 and '' or 'e'),
zielTyp = 'fahrzeuge',
}
end
return {
kennzahlen = {
imDienst = #dienstliste,
verfuegbar = nachStatus.verfuegbar or 0,
imEinsatz = nachStatus.einsatz or 0,
pause = nachStatus.pause or 0,
einsaetzeOffen = #einsaetze,
anzeigenOffen = anzeigenOffen,
fahndungen = #fahndungen,
gefangene = #gewahrsam,
haftAbgelaufen = abgelaufen,
berichteOffen = #eigeneBerichte,
akteFreigabe = akteFreigabe,
fahrzeugeGesamt = #fahrzeuge,
fahrzeugeEinsatz= fahrzeugeImEinsatz,
},
beamte = dienstliste,
einsaetze = einsaetze,
fahndungen = fahndungen,
gewahrsam = gewahrsam,
berichte = eigeneBerichte,
fahrzeuge = fahrzeuge,
mitteilungen = mitteilungen,
warnungen = warnungen,
hinweise = Hinweise.Lade(charakterId, 15),
}
end)
--- Markiert einen Hinweis als gelesen
Kern.RegistriereAktion('dashboard', 'hinweisGelesen', nil, function(quelle, beamter, daten)
if type(daten.id) ~= 'number' then return nil, 'Ungültige Kennung.' end
Hinweise.MarkiereGelesen(beamter.charakterId, daten.id)
return { ok = true }
end)