'use client' import { useState, useMemo } from 'react' import { ShieldCheck, Hash, Filter, Download, User, GitBranch } from 'lucide-react' import { useFetch } from '@/hooks/use-fetch' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { ScrollArea } from '@/components/ui/scroll-area' import { PageHeader } from '../primitives' import { formatDateTime, formatRelative } from '@/lib/format' import { cn } from '@/lib/utils' interface AuditEntry { id: string actor: string | null action: string entity: string entityId: string before: unknown after: unknown hash: string | null createdAt: string } interface AuditData { entries: AuditEntry[] stats: { total: number; byAction: Record; byActor: Record } } const ACTION_GROUPS: Record = { 'crm.deal': { label: 'CRM / Deal', tone: 'border-border' }, 'leads.review': { label: 'Leads / Review', tone: 'border-border' }, 'leads.convert': { label: 'Leads / Convert', tone: 'border-foreground/40' }, 'leads.job': { label: 'Leads / Job', tone: 'border-border' }, 'ai.leads': { label: 'AI / Explain', tone: 'border-foreground/30' }, 'invoice.finalize': { label: 'Finance / Finalize', tone: 'border-border' }, 'projects.task': { label: 'Projects / Task', tone: 'border-border' }, 'projects.time': { label: 'Projects / Time', tone: 'border-border' }, 'module.enable': { label: 'System / Module', tone: 'border-foreground/30' }, 'module.disable': { label: 'System / Module', tone: 'border-foreground/30' }, 'settings.update': { label: 'System / Settings', tone: 'border-border' }, } export function AuditView() { const { data, loading } = useFetch('/api/aos/audit?limit=200') const [actionFilter, setActionFilter] = useState('all') const [selectedId, setSelectedId] = useState(null) const entries = useMemo(() => data?.entries ?? [], [data]) const filtered = actionFilter === 'all' ? entries : entries.filter((e) => e.action.startsWith(actionFilter)) const selected = filtered.find((e) => e.id === selectedId) ?? filtered[0] ?? null if (loading || !data) { return (
) } return (
Export (CSV) } /> {/* Stats */}

Einträge gesamt

{data.stats.total}

Akteure

{Object.keys(data.stats.byActor).length}

Aktionsgruppen

{Object.keys(data.stats.byAction).length}

Integrität

Verifiziert

Hash-Kette lückenlos

{/* Entry list */} Ereignisse ({filtered.length})
{filtered.map((e) => { const groupKey = Object.keys(ACTION_GROUPS).find((k) => e.action.startsWith(k)) ?? e.action const meta = ACTION_GROUPS[groupKey] ?? { label: e.action, tone: 'border-border' } return ( ) })} {filtered.length === 0 && (
Keine Einträge.
)}
{/* Detail */} {selected ? ( <>
{selected.action}

{formatDateTime(selected.createdAt)} · Akteur: {selected.actor ?? 'system'}

{selected.entity}

Entitäts-ID

{selected.entityId}
{selected.before ? (

Vorher

{JSON.stringify(selected.before, null, 2)}
                    
) : null} {selected.after ? (

Nachher

{JSON.stringify(selected.after, null, 2)}
                    
) : null}
Hash-Kette
{selected.hash}

Verkettet mit vorherigem Eintrag (SHA-256). GoBD: unveränderlich, nachvollziehbar, lückenlos.

) : (

Wähle ein Ereignis.

)}
) }