'use client' import { useState } from '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' import { toast } from 'sonner' import { Database, Download, FileJson, FileSpreadsheet, ShieldCheck, Lock, Clock, CheckCircle2, AlertTriangle, Loader2, } from 'lucide-react' interface InventoryItem { category: string models: string[] count: number retentionYears: number | null legalBasis: string } interface ExportEntry { id: string action: string detail: { format: string; scope: string; checksum: string; sizeBytes: number; recordCount: number } | null actor: string | null createdAt: string } interface DataData { inventory: InventoryItem[] stats: { totalRecords: number; activeTokens: number; activeWebhooks: number; exportCount: number } exports: ExportEntry[] } export function DataView() { const { data, loading, refetch } = useFetch('/api/aos/data-management') const [exporting, setExporting] = useState<'json-full' | 'csv-full' | null>(null) if (loading || !data) { return (
) } const { inventory, stats, exports } = data const doExport = async (format: 'json' | 'csv', scope: string) => { setExporting(format === 'json' ? 'json-full' : 'csv-full') const t = toast.loading('Export wird erstellt (SHA-256 Checksumme berechnet)…') try { const r = await fetch(`/api/aos/data-management/export?format=${format}&scope=${scope}`) if (!r.ok) throw new Error('export failed') const blob = await r.blob() const checksum = r.headers.get('x-aos-checksum') ?? 'n/a' const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `aos-export-${scope}-${new Date().toISOString().slice(0, 10)}.${format === 'json' ? 'json' : 'csv'}` a.click() URL.revokeObjectURL(url) toast.success(`Export erstellt — Checksumme: ${checksum.slice(0, 24)}…`, { id: t, duration: 6000 }) refetch() } catch { toast.error('Export fehlgeschlagen', { id: t }) } finally { setExporting(null) } } return (
{/* KPIs */}

Datensätze gesamt

{stats.totalRecords}

Exporte (Historie)

{stats.exportCount}

Aktive API-Tokens

{stats.activeTokens}

Aktive Webhooks

{stats.activeWebhooks}

{/* Export actions */} Daten-Export (DSGVO Art. 15 / Art. 20)

DSGVO Art. 15 + Art. 20

Betroffene haben das Recht auf Auskunft über ihre gespeicherten Daten (Art. 15) und deren Übertragbarkeit in einem strukturierten, gängigen Format (Art. 20). Jeder Export wird mit SHA-256-Prüfsumme signiert und im Audit-Log protokolliert.

{/* Data inventory (Art. 30) */} Verarbeitungsverzeichnis (Art. 30 DSGVO)
{inventory.map((item) => ( ))}
Kategorie Modelle Datensätze Aufbewahrung Rechtsgrundlage
{item.category}
{item.models.map((m) => {m})}
{item.count} {item.retentionYears ? ( {item.retentionYears} Jahre ) : ( bis Widerruf )} {item.legalBasis}
Gesamt {stats.totalRecords}
{/* Export history */} Export-Historie ({exports.length}) {exports.length === 0 &&

Noch keine Exporte.

} {exports.map((exp) => (

{exp.action}

{exp.detail && (

Format: {exp.detail.format} · Scope: {exp.detail.scope} · {exp.detail.recordCount} Datensätze · {(exp.detail.sizeBytes / 1024).toFixed(1)} KB

)} {exp.detail && (

SHA-256: {exp.detail.checksum.slice(0, 32)}…

)}
{formatRelative(exp.createdAt)}
))}
{/* Retention + Erasure notice */}
Aufbewahrungsfristen
Rechnungen & Buchhaltungsdaten8 Jahre (GoBD)
Geschäftsbriefe & Verträge6 Jahre (GoBD)
Audit-Log (Hash-Kette)8 Jahre (GoBD)
Kundenstammdaten6 Jahre

Automatische Lösch-Jobs (cron) nach Ablauf der Frist. GoBD: Daten sind unveränderlich, Korrekturen als Storno.

Löschung (Art. 17 DSGVO)

Löschbegehren nach Art. 17 DSGVO (Recht auf Vergessenwerden) müssen geprüft werden:

  • GoBD-Aufbewahrungspflicht (8/6 Jahre) hat Vorrang
  • Aktive Verträge/Rechnungen können nicht gelöscht werden
  • Stattdessen: Pseudonymisierung (Name → „gelöscht")
  • Vollständige Löschung erst nach Ablauf aller Fristen

Lösch-Anfragen werden im Audit-Log protokolliert und erfordern Freigabe durch den Datenschutzbeauftragten.

) }