import { Modal } from "@/components/modal"; import { Pill } from "@/components/mockup-ui"; /** * Wiederverwendbarer Audit-Trail (Aktivitätsprotokoll) als server-gerendertes * Popup — gleiches Muster wie die übrigen Popups (searchParam öffnet/schließt, * kein Client-State). Wird an zwei Stellen eingebunden: * - Mandanten-Einstellungen (/settings?audit=1): eigener Mandant, RLS-gefiltert. * - Plattform-Konsole je Mandant (/admin/[id]?audit=1): Superadmin liest den * Trail des jeweiligen Mandanten cross-tenant über den Owner-Client. * Die Zeilen werden bereits gefiltert/aufbereitet übergeben — die Komponente * rendert nur; das hält den Datenpfad (Mandant vs. Plattform) in der Seite. */ export type AuditRow = { id: string; createdAt: Date; actorId: string | null; action: string; entity: string; entityId: string | null; scope: string; }; const ACTION_LABEL: Record = { create: "Erstellt", update: "Geändert", delete: "Gelöscht", login: "Anmeldung", logout: "Abmeldung", denied: "Abgelehnt", export: "Export", import: "Import", approve: "Freigegeben", reject: "Zurückgewiesen", }; const ACTION_TONE: Record = { create: "ok", update: "info", approve: "ok", delete: "warn", denied: "warn", reject: "warn", login: "mut", logout: "mut", }; const ENTITY_LABEL: Record = { user: "Benutzer", role: "Rolle", tenant: "Mandant", tenant_settings: "Einstellungen", tenant_module: "Modul", tenant_locale: "Mandantensprache", tenant_mfa_policy: "MFA-Pflicht", module: "Modul", session: "Sitzung", platformAdmin: "Plattform-Admin", // Craftvia-Fachobjekte (Labels vorab, Module folgen) customer: "Kunde", site: "Objekt", team: "Team", work_order: "Auftrag", import: "Auftragsimport", report: "Bericht", emergency: "Notdienst", document: "Dokument", }; const fmt = new Intl.DateTimeFormat("de-DE", { dateStyle: "medium", timeStyle: "short" }); function actionLabel(a: string) { return ACTION_LABEL[a] ?? a.charAt(0).toUpperCase() + a.slice(1); } function entityLabel(e: string) { return ENTITY_LABEL[e] ?? e; } export function AuditTrailModal({ rows, actorNames, closeHref, sub, }: { rows: AuditRow[]; actorNames: Record; closeHref: string; sub?: string; }) { return (
{rows.length === 0 ? (

Noch keine Einträge protokolliert.

) : ( {rows.map((r) => ( ))}
Zeitpunkt Akteur Aktion Objekt
{fmt.format(r.createdAt)} {r.actorId ? actorNames[r.actorId] ?? "System" : "System"} {actionLabel(r.action)} {entityLabel(r.entity)} {r.entityId && ( #{r.entityId.slice(0, 8)} )} {r.scope === "platform" && ( · Plattform )}
)}
); }