'use client' import React, { useState } from 'react' import Link from 'next/link' import { usePathname } from 'next/navigation' import { useSDK } from '@/lib/sdk' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' import { Database, FileText, Cookie, Clock, LayoutGrid, X, History, Shield, AlertTriangle, CheckCircle, XCircle, Monitor, Globe, Calendar, User, FileCheck, } from 'lucide-react' // ============================================================================= // NAVIGATION TABS // ============================================================================= const EINWILLIGUNGEN_TABS = [ { id: 'overview', label: 'Übersicht', href: '/sdk/einwilligungen', icon: LayoutGrid, description: 'Consent-Tracking Dashboard', }, { id: 'catalog', label: 'Datenpunktkatalog', href: '/sdk/einwilligungen/catalog', icon: Database, description: '18 Kategorien, 128 Datenpunkte', }, { id: 'privacy-policy', label: 'DSI Generator', href: '/sdk/einwilligungen/privacy-policy', icon: FileText, description: 'Datenschutzinformation erstellen', }, { id: 'cookie-banner', label: 'Cookie-Banner', href: '/sdk/einwilligungen/cookie-banner', icon: Cookie, description: 'Cookie-Consent konfigurieren', }, { id: 'retention', label: 'Löschmatrix', href: '/sdk/einwilligungen/retention', icon: Clock, description: 'Aufbewahrungsfristen verwalten', }, ] function EinwilligungenNavTabs() { const pathname = usePathname() return (
{EINWILLIGUNGEN_TABS.map((tab) => { const Icon = tab.icon const isActive = pathname === tab.href return (
{tab.label}
{tab.description}
) })}
) } // ============================================================================= // TYPES // ============================================================================= type ConsentType = 'marketing' | 'analytics' | 'newsletter' | 'terms' | 'privacy' | 'cookies' type ConsentStatus = 'granted' | 'withdrawn' | 'pending' type HistoryAction = 'granted' | 'withdrawn' | 'version_update' | 'renewed' interface ConsentHistoryEntry { id: string action: HistoryAction timestamp: Date version: string documentTitle?: string ipAddress: string userAgent: string source: string notes?: string } interface ConsentRecord { id: string identifier: string email: string firstName?: string lastName?: string consentType: ConsentType status: ConsentStatus currentVersion: string grantedAt: Date | null withdrawnAt: Date | null source: string | null ipAddress: string userAgent: string history: ConsentHistoryEntry[] } // ============================================================================= // HELPER FUNCTIONS // ============================================================================= const typeLabels: Record = { marketing: 'Marketing', analytics: 'Analyse', newsletter: 'Newsletter', terms: 'AGB', privacy: 'Datenschutz', cookies: 'Cookies', } const typeColors: Record = { marketing: 'bg-purple-100 text-purple-700', analytics: 'bg-blue-100 text-blue-700', newsletter: 'bg-green-100 text-green-700', terms: 'bg-yellow-100 text-yellow-700', privacy: 'bg-orange-100 text-orange-700', cookies: 'bg-pink-100 text-pink-700', } const statusColors: Record = { granted: 'bg-green-100 text-green-700', withdrawn: 'bg-red-100 text-red-700', pending: 'bg-yellow-100 text-yellow-700', } const statusLabels: Record = { granted: 'Erteilt', withdrawn: 'Widerrufen', pending: 'Ausstehend', } const actionLabels: Record = { granted: 'Einwilligung erteilt', withdrawn: 'Einwilligung widerrufen', version_update: 'Neue Version akzeptiert', renewed: 'Einwilligung erneuert', } const actionIcons: Record = { granted: , withdrawn: , version_update: , renewed: , } function formatDateTime(date: Date): string { return date.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', }) } function formatDate(date: Date | null): string { if (!date) return '-' return date.toLocaleDateString('de-DE') } // ============================================================================= // DETAIL MODAL COMPONENT // ============================================================================= interface ConsentDetailModalProps { record: ConsentRecord onClose: () => void onRevoke: (recordId: string) => void } function ConsentDetailModal({ record, onClose, onRevoke }: ConsentDetailModalProps) { const [showRevokeConfirm, setShowRevokeConfirm] = useState(false) return (
{/* Header */}

Consent-Details

{record.email}

{/* Content */}
{/* User Info */}
Benutzerinformationen
Name: {record.firstName} {record.lastName}
E-Mail: {record.email}
User-ID: {record.identifier}
Consent-Status
Typ: {typeLabels[record.consentType]}
Status: {statusLabels[record.status]}
Version: v{record.currentVersion}
{/* Technical Details */}
Technische Details (letzter Consent)
IP-Adresse
{record.ipAddress}
Quelle
{record.source ?? '—'}
User-Agent
{record.userAgent}
{/* History Timeline */}
Consent-Historie {record.history.length} Einträge
{/* Timeline line */}
{record.history.map((entry, index) => (
{/* Icon */}
{actionIcons[entry.action]}
{/* Content */}
{actionLabels[entry.action]}
{entry.documentTitle && (
{entry.documentTitle}
)}
v{entry.version}
{formatDateTime(entry.timestamp)}
{entry.ipAddress}
Quelle: {entry.source}
{entry.notes && (
{entry.notes}
)} {/* Expandable User-Agent */}
User-Agent anzeigen
{entry.userAgent}
))}
{/* Footer Actions */}
Consent-ID: {record.id}
{record.status === 'granted' && !showRevokeConfirm && ( )} {showRevokeConfirm && (
Wirklich widerrufen?
)}
) } // ============================================================================= // TABLE ROW COMPONENT // ============================================================================= interface ConsentRecordRowProps { record: ConsentRecord onShowDetails: (record: ConsentRecord) => void } function ConsentRecordRow({ record, onShowDetails }: ConsentRecordRowProps) { return (
{record.email}
{record.identifier}
{typeLabels[record.consentType]} {statusLabels[record.status]} {formatDate(record.grantedAt)} {formatDate(record.withdrawnAt)} v{record.currentVersion}
{record.history.length}
) } // ============================================================================= // MAIN PAGE // ============================================================================= export default function EinwilligungenPage() { const { state } = useSDK() const [records, setRecords] = useState([]) const [filter, setFilter] = useState('all') const [searchQuery, setSearchQuery] = useState('') const [selectedRecord, setSelectedRecord] = useState(null) const [isLoading, setIsLoading] = useState(true) const PAGE_SIZE = 50 const [currentPage, setCurrentPage] = useState(1) const [totalRecords, setTotalRecords] = useState(0) const [globalStats, setGlobalStats] = useState({ total: 0, active: 0, revoked: 0 }) const loadConsents = React.useCallback(async (page: number) => { setIsLoading(true) try { const offset = (page - 1) * PAGE_SIZE const listResponse = await fetch( `/api/sdk/v1/einwilligungen/consent?limit=${PAGE_SIZE}&offset=${offset}` ) if (listResponse.ok) { const listData = await listResponse.json() setTotalRecords(listData.total ?? 0) if (listData.consents?.length > 0) { const mapped: ConsentRecord[] = listData.consents.map((c: { id: string user_id: string data_point_id: string granted: boolean granted_at: string revoked_at?: string consent_version?: string source?: string ip_address?: string user_agent?: string history?: Array<{ id: string action: string created_at: string consent_version?: string ip_address?: string user_agent?: string source?: string }> }) => ({ id: c.id, identifier: c.user_id, email: c.user_id, consentType: (c.data_point_id as ConsentType) || 'privacy', status: (c.revoked_at ? 'withdrawn' : 'granted') as ConsentStatus, currentVersion: c.consent_version || '1.0', grantedAt: c.granted_at ? new Date(c.granted_at) : null, withdrawnAt: c.revoked_at ? new Date(c.revoked_at) : null, source: c.source ?? null, ipAddress: c.ip_address ?? '', userAgent: c.user_agent ?? '', history: (c.history ?? []).map(h => ({ id: h.id, action: h.action as HistoryAction, timestamp: new Date(h.created_at), version: h.consent_version || '1.0', ipAddress: h.ip_address ?? '', userAgent: h.user_agent ?? '', source: h.source ?? '', })), })) setRecords(mapped) } else { setRecords([]) } } } catch { // Backend nicht erreichbar, leere Liste anzeigen } finally { setIsLoading(false) } }, []) const loadStats = React.useCallback(async () => { try { const res = await fetch('/api/sdk/v1/einwilligungen/consent?stats=true') if (res.ok) { const data = await res.json() const s = data.statistics if (s) { setGlobalStats({ total: s.total_consents ?? 0, active: s.active_consents ?? 0, revoked: s.revoked_consents ?? 0, }) setTotalRecords(s.total_consents ?? 0) } } } catch { // Statistiken nicht erreichbar — lokale Werte behalten } }, []) React.useEffect(() => { loadStats() }, [loadStats]) React.useEffect(() => { loadConsents(currentPage) }, [currentPage, loadConsents]) const filteredRecords = records.filter(record => { const matchesFilter = filter === 'all' || record.consentType === filter || record.status === filter const matchesSearch = searchQuery === '' || record.email.toLowerCase().includes(searchQuery.toLowerCase()) || record.identifier.toLowerCase().includes(searchQuery.toLowerCase()) return matchesFilter && matchesSearch }) const versionUpdates = records.reduce((acc, r) => acc + r.history.filter(h => h.action === 'version_update').length, 0) const handleRevoke = async (recordId: string) => { try { const response = await fetch('/api/sdk/v1/einwilligungen/consent', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ consentId: recordId, action: 'revoke' }), }) if (response.ok) { const now = new Date() setRecords(prev => prev.map(r => { if (r.id === recordId) { return { ...r, status: 'withdrawn' as ConsentStatus, withdrawnAt: now, history: [ ...r.history, { id: `h-${recordId}-${r.history.length + 1}`, action: 'withdrawn' as HistoryAction, timestamp: now, version: r.currentVersion, ipAddress: 'Admin-Portal', userAgent: 'Admin Action', source: 'Manueller Widerruf durch Admin', notes: 'Widerruf über Admin-Portal durchgeführt', }, ], } } return r })) loadStats() } } catch { // Fallback: update local state even if API call fails const now = new Date() setRecords(prev => prev.map(r => { if (r.id === recordId) { return { ...r, status: 'withdrawn' as ConsentStatus, withdrawnAt: now } } return r })) } } const stepInfo = STEP_EXPLANATIONS['einwilligungen'] return (
{/* Step Header */} {/* Navigation Tabs */} {/* Stats */}
Gesamt
{globalStats.total}
Aktive Einwilligungen
{globalStats.active}
Widerrufen
{globalStats.revoked}
Versions-Updates
{versionUpdates}
{/* Info Banner */}
Consent-Historie aktiviert
Alle Änderungen an Einwilligungen werden protokolliert, inkl. Zustimmungen zu neuen Versionen von AGB, DSI und anderen Dokumenten. Klicken Sie auf "Details" um die vollständige Historie eines Nutzers einzusehen.
{/* Search and Filter */}
setSearchQuery(e.target.value)} placeholder="E-Mail oder User-ID suchen..." className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
{['all', 'granted', 'withdrawn', 'terms', 'privacy', 'cookies', 'marketing', 'analytics'].map(f => ( ))}
{/* Records Table */}
{filteredRecords.map(record => ( ))}
Nutzer Typ Status Erteilt am Widerrufen am Version Historie Aktion
{filteredRecords.length === 0 && (

Keine Einträge gefunden

Passen Sie die Suche oder den Filter an.

)}
{/* Pagination */} {(() => { const totalPages = Math.ceil(totalRecords / PAGE_SIZE) return (

Zeige {totalRecords === 0 ? 0 : (currentPage - 1) * PAGE_SIZE + 1}– {Math.min(currentPage * PAGE_SIZE, totalRecords)} von {totalRecords} Einträgen

{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { const page = Math.max(1, Math.min(currentPage - 2, totalPages - 4)) + i if (page < 1 || page > totalPages) return null return ( ) })}
) })()} {/* Detail Modal */} {selectedRecord && ( setSelectedRecord(null)} onRevoke={handleRevoke} /> )}
) }