import type { Metadata } from 'next' import { ShieldCheck } from 'lucide-react' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Badge } from '@/components/ui/badge' import { Card, CardContent } from '@/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { SeitenKopf, Leerzustand } from '@/components/seiten-kopf' import { aktiveBetreute, erfordereBenutzer, erfordereZugang } from '@/lib/auth/sitzung' import { db } from '@/lib/db' import { entschluesselnWennMoeglich } from '@/lib/crypto/feld' import { formatDatumZeit } from '@/lib/utils' import type { AuditAktion } from '@prisma/client' export const metadata: Metadata = { title: 'Protokoll' } export const dynamic = 'force-dynamic' const AKTION_BESCHRIFTUNG: Record = { ERSTELLT: 'angelegt', GEAENDERT: 'geändert', GELOESCHT: 'in den Papierkorb', WIEDERHERGESTELLT: 'wiederhergestellt', ENDGUELTIG_GELOESCHT: 'endgültig gelöscht', ANGESEHEN: 'angesehen', EXPORTIERT: 'exportiert', DOKUMENT_HERUNTERGELADEN: 'Dokument geöffnet', ANMELDUNG: 'angemeldet', ANMELDUNG_FEHLGESCHLAGEN: 'Anmeldung fehlgeschlagen', ABMELDUNG: 'abgemeldet', ZWEITER_FAKTOR_EINGERICHTET: 'zweiter Faktor eingerichtet', PASSKEY_REGISTRIERT: 'Passkey eingerichtet', PASSKEY_ENTFERNT: 'Passkey entfernt', PASSWORT_GEAENDERT: 'Passwort geändert', ROLLE_GEAENDERT: 'Rolle geändert', FREIGABE_GEAENDERT: 'Freigabe geändert', } function variante(aktion: AuditAktion) { if (aktion === 'ANMELDUNG_FEHLGESCHLAGEN' || aktion === 'ENDGUELTIG_GELOESCHT') return 'rot' if (aktion === 'GELOESCHT' || aktion === 'EXPORTIERT' || aktion === 'PASSKEY_ENTFERNT') return 'orange' if (aktion === 'ERSTELLT') return 'gruen' return 'secondary' } type Aenderung = Record function alsText(wert: unknown): string { if (wert === null || wert === undefined || wert === '') return '—' if (typeof wert === 'boolean') return wert ? 'ja' : 'nein' if (Array.isArray(wert)) return wert.length ? wert.join(', ') : '—' return String(wert) } export default async function ProtokollSeite({ searchParams, }: { searchParams: Promise<{ seite?: string }> }) { const benutzer = await erfordereBenutzer() const aktive = await aktiveBetreute(benutzer) if (!aktive) return null await erfordereZugang(aktive.id, 'STAMMDATEN') const { seite = '1' } = await searchParams const proSeite = 50 const nummer = Math.max(1, Number.parseInt(seite, 10) || 1) /* * Zwei Arten von Eintraegen gehoeren hierher: * * 1. Alles, was diese Betreuung betrifft. * 2. Anmeldungen, Abmeldungen und Kontoaenderungen des angemeldeten * Benutzers. Die tragen keine betreuteId - sie haengen ja am Konto, * nicht an der Akte. Wuerde man nur nach betreuteId filtern, fehlte * ausgerechnet die Frage "wer hat wann auf die Akte zugegriffen". */ const filter = { OR: [{ betreuteId: aktive.id }, { userId: benutzer.id, betreuteId: null }], } const [eintraege, gesamt] = await Promise.all([ db.auditLog.findMany({ where: filter, orderBy: { zeitpunkt: 'desc' }, skip: (nummer - 1) * proSeite, take: proSeite, }), db.auditLog.count({ where: filter }), ]) const seiten = Math.max(1, Math.ceil(gesamt / proSeite)) return (
{eintraege.length === 0 ? ( ) : ( Zeitpunkt Wer Was Gegenstand {eintraege.map((eintrag) => { const roh = entschluesselnWennMoeglich( eintrag.aenderungVerschluesselt, 'aenderungVerschluesselt', ) let aenderung: Aenderung | null = null try { aenderung = roh ? (JSON.parse(roh) as Aenderung) : null } catch { aenderung = null } return ( {formatDatumZeit(eintrag.zeitpunkt)} {eintrag.userName ?? '—'} {eintrag.userEmail} {AKTION_BESCHRIFTUNG[eintrag.aktion]} {eintrag.entitaet} {eintrag.bezeichnung && ( · {eintrag.bezeichnung} )} {aenderung && Object.keys(aenderung).length > 0 && (
{Object.keys(aenderung).length} Feld {Object.keys(aenderung).length === 1 ? '' : 'er'} geändert
{Object.entries(aenderung).map(([feld, werte]) => (
{feld}
{alsText(werte.alt)} {alsText(werte.neu)}
))}
)}
) })}
)} {seiten > 1 && ( )}
) }