'use client' /** * VVT — Verarbeitungsverzeichnis (Art. 30 DSGVO) * * 4 Tabs: * 1. Verzeichnis (Uebersicht aller Verarbeitungstaetigkeiten) * 2. Verarbeitung bearbeiten (Detail-Editor) * 3. Generator (Profiling-Fragebogen) * 4. Export & Compliance */ import { useState, useEffect, useCallback } from 'react' import { useSDK } from '@/lib/sdk' import StepHeader, { STEP_EXPLANATIONS } from '@/components/sdk/StepHeader/StepHeader' import { DATA_SUBJECT_CATEGORY_META, PERSONAL_DATA_CATEGORY_META, LEGAL_BASIS_META, TRANSFER_MECHANISM_META, ART9_CATEGORIES, BUSINESS_FUNCTION_LABELS, STATUS_LABELS, STATUS_COLORS, PROTECTION_LEVEL_LABELS, DEPLOYMENT_LABELS, REVIEW_INTERVAL_LABELS, createEmptyActivity, createDefaultOrgHeader, generateVVTId, isSpecialCategory, } from '@/lib/sdk/vvt-types' import type { VVTActivity, VVTOrganizationHeader, BusinessFunction } from '@/lib/sdk/vvt-types' import { PROFILING_STEPS, PROFILING_QUESTIONS, getQuestionsForStep, getStepProgress, getTotalProgress, generateActivities, } from '@/lib/sdk/vvt-profiling' import type { ProfilingAnswers } from '@/lib/sdk/vvt-profiling' // ============================================================================= // CONSTANTS // ============================================================================= type Tab = 'verzeichnis' | 'editor' | 'generator' | 'export' const STORAGE_KEY = 'bp_vvt' interface VVTData { activities: VVTActivity[] orgHeader: VVTOrganizationHeader profilingAnswers: ProfilingAnswers } function loadData(): VVTData { if (typeof window === 'undefined') return { activities: [], orgHeader: createDefaultOrgHeader(), profilingAnswers: {} } try { const stored = localStorage.getItem(STORAGE_KEY) if (stored) return JSON.parse(stored) } catch { /* ignore */ } return { activities: [], orgHeader: createDefaultOrgHeader(), profilingAnswers: {} } } function saveData(data: VVTData) { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)) } // ============================================================================= // MAIN PAGE // ============================================================================= export default function VVTPage() { const { state } = useSDK() const [tab, setTab] = useState('verzeichnis') const [activities, setActivities] = useState([]) const [orgHeader, setOrgHeader] = useState(createDefaultOrgHeader()) const [profilingAnswers, setProfilingAnswers] = useState({}) const [editingId, setEditingId] = useState(null) const [filter, setFilter] = useState('all') const [searchQuery, setSearchQuery] = useState('') const [sortBy, setSortBy] = useState<'name' | 'date' | 'status'>('name') const [generatorStep, setGeneratorStep] = useState(1) const [generatorPreview, setGeneratorPreview] = useState(null) // Load from localStorage useEffect(() => { const data = loadData() setActivities(data.activities) setOrgHeader(data.orgHeader) setProfilingAnswers(data.profilingAnswers) }, []) // Save to localStorage on change const persist = useCallback((acts: VVTActivity[], org: VVTOrganizationHeader, prof: ProfilingAnswers) => { saveData({ activities: acts, orgHeader: org, profilingAnswers: prof }) }, []) const updateActivities = useCallback((acts: VVTActivity[]) => { setActivities(acts) persist(acts, orgHeader, profilingAnswers) }, [orgHeader, profilingAnswers, persist]) const updateOrgHeader = useCallback((org: VVTOrganizationHeader) => { setOrgHeader(org) persist(activities, org, profilingAnswers) }, [activities, profilingAnswers, persist]) const updateProfilingAnswers = useCallback((prof: ProfilingAnswers) => { setProfilingAnswers(prof) persist(activities, orgHeader, prof) }, [activities, orgHeader, persist]) // Computed stats const activeCount = activities.filter(a => a.status === 'APPROVED').length const draftCount = activities.filter(a => a.status === 'DRAFT').length const thirdCountryCount = activities.filter(a => a.thirdCountryTransfers.length > 0).length const art9Count = activities.filter(a => a.personalDataCategories.some(c => ART9_CATEGORIES.includes(c))).length // Filtered & sorted activities const filteredActivities = activities .filter(a => { const matchesFilter = filter === 'all' || a.status === filter || (filter === 'thirdcountry' && a.thirdCountryTransfers.length > 0) || (filter === 'art9' && a.personalDataCategories.some(c => ART9_CATEGORIES.includes(c))) const matchesSearch = searchQuery === '' || a.name.toLowerCase().includes(searchQuery.toLowerCase()) || a.description.toLowerCase().includes(searchQuery.toLowerCase()) || a.vvtId.toLowerCase().includes(searchQuery.toLowerCase()) return matchesFilter && matchesSearch }) .sort((a, b) => { if (sortBy === 'name') return a.name.localeCompare(b.name) if (sortBy === 'date') return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() return a.status.localeCompare(b.status) }) const editingActivity = editingId ? activities.find(a => a.id === editingId) : null const stepInfo = STEP_EXPLANATIONS['vvt'] // Tab buttons const tabs: { id: Tab; label: string; count?: number }[] = [ { id: 'verzeichnis', label: 'Verzeichnis', count: activities.length }, { id: 'editor', label: 'Verarbeitung bearbeiten' }, { id: 'generator', label: 'Generator' }, { id: 'export', label: 'Export & Compliance' }, ] return (
{/* Tab Navigation */}
{tabs.map(t => ( ))}
{/* Tab Content */} {tab === 'verzeichnis' && ( { setEditingId(id); setTab('editor') }} onNew={() => { const vvtId = generateVVTId(activities.map(a => a.vvtId)) const newAct = createEmptyActivity(vvtId) updateActivities([...activities, newAct]) setEditingId(newAct.id) setTab('editor') }} onDelete={(id) => updateActivities(activities.filter(a => a.id !== id))} /> )} {tab === 'editor' && ( { const idx = activities.findIndex(a => a.id === updated.id) if (idx >= 0) { const copy = [...activities] copy[idx] = { ...updated, updatedAt: new Date().toISOString() } updateActivities(copy) } }} onBack={() => setTab('verzeichnis')} onSelectActivity={(id) => setEditingId(id)} /> )} {tab === 'generator' && ( { updateActivities([...activities, ...newActivities]) setGeneratorPreview(null) setGeneratorStep(1) setTab('verzeichnis') }} /> )} {tab === 'export' && ( )}
) } // ============================================================================= // TAB 1: VERZEICHNIS // ============================================================================= function TabVerzeichnis({ activities, allActivities, activeCount, draftCount, thirdCountryCount, art9Count, filter, setFilter, searchQuery, setSearchQuery, sortBy, setSortBy, onEdit, onNew, onDelete, }: { activities: VVTActivity[] allActivities: VVTActivity[] activeCount: number draftCount: number thirdCountryCount: number art9Count: number filter: string setFilter: (f: string) => void searchQuery: string setSearchQuery: (q: string) => void sortBy: string setSortBy: (s: 'name' | 'date' | 'status') => void onEdit: (id: string) => void onNew: () => void onDelete: (id: string) => void }) { return (
{/* Stats */}
{/* Search + Filter + New */}
setSearchQuery(e.target.value)} placeholder="VVT-ID, Name oder Beschreibung 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" />
{[ { key: 'all', label: 'Alle' }, { key: 'DRAFT', label: 'Entwurf' }, { key: 'REVIEW', label: 'Pruefung' }, { key: 'APPROVED', label: 'Genehmigt' }, { key: 'thirdcountry', label: 'Drittland' }, { key: 'art9', label: 'Art. 9' }, ].map(f => ( ))}
{/* Activity Cards */}
{activities.map(activity => ( ))}
{activities.length === 0 && (

Keine Verarbeitungen gefunden

Erstellen Sie eine neue Verarbeitung manuell oder nutzen Sie den Generator, um automatisch Eintraege aus einem Fragebogen zu erzeugen.

)}
) } function StatCard({ label, value, color }: { label: string; value: number; color: string }) { const borderColors: Record = { gray: 'border-gray-200', green: 'border-green-200', yellow: 'border-yellow-200', orange: 'border-orange-200', red: 'border-red-200', } const textColors: Record = { gray: 'text-gray-600', green: 'text-green-600', yellow: 'text-yellow-600', orange: 'text-orange-600', red: 'text-red-600', } return (
{label}
{value}
) } function ActivityCard({ activity, onEdit, onDelete }: { activity: VVTActivity; onEdit: (id: string) => void; onDelete: (id: string) => void }) { const hasArt9 = activity.personalDataCategories.some(c => ART9_CATEGORIES.includes(c)) const hasThirdCountry = activity.thirdCountryTransfers.length > 0 return (
{activity.vvtId} {STATUS_LABELS[activity.status] || activity.status} {hasArt9 && ( Art. 9 )} {hasThirdCountry && ( Drittland )} {activity.dpiaRequired && ( DSFA )}

{activity.name || '(Ohne Namen)'}

{activity.description && (

{activity.description}

)}
{BUSINESS_FUNCTION_LABELS[activity.businessFunction]} {activity.responsible || 'Kein Verantwortlicher'} Aktualisiert: {new Date(activity.updatedAt).toLocaleDateString('de-DE')}
) } // ============================================================================= // TAB 2: EDITOR // ============================================================================= function TabEditor({ activity, activities, onSave, onBack, onSelectActivity, }: { activity: VVTActivity | null | undefined activities: VVTActivity[] onSave: (updated: VVTActivity) => void onBack: () => void onSelectActivity: (id: string) => void }) { const [local, setLocal] = useState(null) const [showAdvanced, setShowAdvanced] = useState(false) useEffect(() => { setLocal(activity ? { ...activity } : null) }, [activity]) if (!local) { return (

Keine Verarbeitung ausgewaehlt

Waehlen Sie eine Verarbeitung aus dem Verzeichnis oder erstellen Sie eine neue.

{activities.length > 0 && (

Verarbeitungen zum Bearbeiten:

{activities.map(a => ( ))}
)}
) } const update = (patch: Partial) => setLocal(prev => prev ? { ...prev, ...patch } : prev) const handleSave = () => { if (local) onSave(local) } return (
{/* Header */}
{local.vvtId}

{local.name || 'Neue Verarbeitung'}

{/* Form */}
{/* Bezeichnung + Beschreibung */} update({ name: e.target.value })} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="z.B. Mitarbeiterverwaltung" />