'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[] } // ============================================================================= // MOCK DATA WITH HISTORY // ============================================================================= const mockRecords: ConsentRecord[] = [ { id: 'c-1', identifier: 'usr-001', email: 'max.mustermann@example.de', firstName: 'Max', lastName: 'Mustermann', consentType: 'terms', status: 'granted', currentVersion: '2.1', grantedAt: new Date('2024-01-15T10:23:45'), withdrawnAt: null, source: 'Website-Formular', ipAddress: '192.168.1.45', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', history: [ { id: 'h-1-1', action: 'granted', timestamp: new Date('2023-06-01T14:30:00'), version: '1.0', documentTitle: 'AGB Version 1.0', ipAddress: '192.168.1.42', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0)', source: 'App-Registrierung', }, { id: 'h-1-2', action: 'version_update', timestamp: new Date('2023-09-15T09:15:00'), version: '1.5', documentTitle: 'AGB Version 1.5 - DSGVO Update', ipAddress: '192.168.1.43', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)', source: 'E-Mail Bestätigung', notes: 'Nutzer hat neuen AGB nach DSGVO-Anpassung zugestimmt', }, { id: 'h-1-3', action: 'version_update', timestamp: new Date('2024-01-15T10:23:45'), version: '2.1', documentTitle: 'AGB Version 2.1 - KI-Klauseln', ipAddress: '192.168.1.45', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', source: 'Website-Formular', notes: 'Zustimmung zu neuen KI-Nutzungsbedingungen', }, ], }, { id: 'c-2', identifier: 'usr-001', email: 'max.mustermann@example.de', firstName: 'Max', lastName: 'Mustermann', consentType: 'marketing', status: 'granted', currentVersion: '1.3', grantedAt: new Date('2024-01-15T10:23:45'), withdrawnAt: null, source: 'Website-Formular', ipAddress: '192.168.1.45', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', history: [ { id: 'h-2-1', action: 'granted', timestamp: new Date('2024-01-15T10:23:45'), version: '1.3', ipAddress: '192.168.1.45', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', source: 'Website-Formular', }, ], }, { id: 'c-3', identifier: 'usr-002', email: 'anna.schmidt@example.de', firstName: 'Anna', lastName: 'Schmidt', consentType: 'newsletter', status: 'withdrawn', currentVersion: '1.2', grantedAt: new Date('2023-11-20T16:45:00'), withdrawnAt: new Date('2024-01-10T08:30:00'), source: 'App', ipAddress: '10.0.0.88', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)', history: [ { id: 'h-3-1', action: 'granted', timestamp: new Date('2023-11-20T16:45:00'), version: '1.2', ipAddress: '10.0.0.88', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)', source: 'App', }, { id: 'h-3-2', action: 'withdrawn', timestamp: new Date('2024-01-10T08:30:00'), version: '1.2', ipAddress: '10.0.0.92', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2)', source: 'Profil-Einstellungen', notes: 'Nutzer hat Newsletter-Abo über Profil deaktiviert', }, ], }, { id: 'c-4', identifier: 'usr-003', email: 'peter.meier@example.de', firstName: 'Peter', lastName: 'Meier', consentType: 'privacy', status: 'granted', currentVersion: '3.0', grantedAt: new Date('2024-01-20T11:00:00'), withdrawnAt: null, source: 'Cookie-Banner', ipAddress: '172.16.0.55', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', history: [ { id: 'h-4-1', action: 'granted', timestamp: new Date('2023-03-10T09:00:00'), version: '2.0', documentTitle: 'Datenschutzerklärung v2.0', ipAddress: '172.16.0.50', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', source: 'Registrierung', }, { id: 'h-4-2', action: 'version_update', timestamp: new Date('2023-08-01T14:00:00'), version: '2.5', documentTitle: 'Datenschutzerklärung v2.5 - Cookie-Update', ipAddress: '172.16.0.52', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', source: 'Cookie-Banner', notes: 'Zustimmung nach Cookie-Richtlinien-Update', }, { id: 'h-4-3', action: 'version_update', timestamp: new Date('2024-01-20T11:00:00'), version: '3.0', documentTitle: 'Datenschutzerklärung v3.0 - AI Act Compliance', ipAddress: '172.16.0.55', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', source: 'Cookie-Banner', notes: 'Neue DSI mit AI Act Transparenzhinweisen', }, ], }, { id: 'c-5', identifier: 'usr-004', email: 'lisa.weber@example.de', firstName: 'Lisa', lastName: 'Weber', consentType: 'analytics', status: 'granted', currentVersion: '1.0', grantedAt: new Date('2024-01-18T13:22:00'), withdrawnAt: null, source: 'Cookie-Banner', ipAddress: '192.168.2.100', userAgent: 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36', history: [ { id: 'h-5-1', action: 'granted', timestamp: new Date('2024-01-18T13:22:00'), version: '1.0', ipAddress: '192.168.2.100', userAgent: 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36', source: 'Cookie-Banner', }, ], }, { id: 'c-6', identifier: 'usr-005', email: 'thomas.klein@example.de', firstName: 'Thomas', lastName: 'Klein', consentType: 'cookies', status: 'granted', currentVersion: '1.8', grantedAt: new Date('2024-01-22T09:15:00'), withdrawnAt: null, source: 'Cookie-Banner', ipAddress: '10.1.0.200', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) Safari/605.1.15', history: [ { id: 'h-6-1', action: 'granted', timestamp: new Date('2023-05-10T10:00:00'), version: '1.0', ipAddress: '10.1.0.150', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0)', source: 'Cookie-Banner', }, { id: 'h-6-2', action: 'withdrawn', timestamp: new Date('2023-08-20T15:30:00'), version: '1.0', ipAddress: '10.1.0.160', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0)', source: 'Cookie-Einstellungen', notes: 'Nutzer hat alle Cookies abgelehnt', }, { id: 'h-6-3', action: 'renewed', timestamp: new Date('2024-01-22T09:15:00'), version: '1.8', ipAddress: '10.1.0.200', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) Safari/605.1.15', source: 'Cookie-Banner', notes: 'Nutzer hat Cookies nach Banner-Redesign erneut akzeptiert', }, ], }, ] // ============================================================================= // 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) React.useEffect(() => { const loadConsents = async () => { try { const response = await fetch('/api/sdk/v1/einwilligungen/consent?stats=true') if (response.ok) { const data = await response.json() // Backend returns stats; actual record list requires separate call const listResponse = await fetch('/api/sdk/v1/einwilligungen/consent') if (listResponse.ok) { const listData = await listResponse.json() // Map backend records to frontend ConsentRecord shape if any returned if (listData.consents && 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?: ConsentHistoryEntry[] }) => ({ 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 ?? [], })) setRecords(mapped) } } } } catch { // Backend not reachable, start with empty list } finally { setIsLoading(false) } } 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 grantedCount = records.filter(r => r.status === 'granted').length const withdrawnCount = records.filter(r => r.status === 'withdrawn').length 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 })) } } 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
{records.length}
Aktive Einwilligungen
{grantedCount}
Widerrufen
{withdrawnCount}
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 placeholder */}

Zeige {filteredRecords.length} von {records.length} Einträgen

{/* Detail Modal */} {selectedRecord && ( setSelectedRecord(null)} onRevoke={handleRevoke} /> )}
) }