'use client' /** * VVT — Verarbeitungsverzeichnis (Art. 30 DSGVO) * * 3 Tabs: * 1. Verzeichnis (Uebersicht + "Aus Scope generieren") * 2. Verarbeitung bearbeiten (Detail-Editor) * 3. Export & Compliance */ import { useState, useEffect } from 'react' import { useSDK } from '@/lib/sdk' import StepHeader, { STEP_EXPLANATIONS } from '@/components/sdk/StepHeader/StepHeader' import { ART9_CATEGORIES, createEmptyActivity, createDefaultOrgHeader, generateVVTId, } from '@/lib/sdk/vvt-types' import type { VVTActivity, VVTOrganizationHeader } from '@/lib/sdk/vvt-types' import { apiListActivities, apiGetOrganization, apiCreateActivity, apiUpdateActivity, apiDeleteActivity, apiUpsertOrganization, } from './_components/api' import { TabVerzeichnis } from './_components/TabVerzeichnis' import { TabEditor } from './_components/TabEditor' import { TabExport } from './_components/TabExport' type Tab = 'verzeichnis' | 'editor' | 'export' export default function VVTPage() { const { state } = useSDK() const [tab, setTab] = useState('verzeichnis') const [activities, setActivities] = useState([]) const [orgHeader, setOrgHeader] = useState(createDefaultOrgHeader()) const [editingId, setEditingId] = useState(null) const [filter, setFilter] = useState('all') const [searchQuery, setSearchQuery] = useState('') const [sortBy, setSortBy] = useState<'name' | 'date' | 'status'>('name') const [isLoading, setIsLoading] = useState(true) const [apiError, setApiError] = useState(null) // Load activities + org header from API useEffect(() => { async function loadFromApi() { setIsLoading(true) setApiError(null) try { const [acts, org] = await Promise.all([ apiListActivities(), apiGetOrganization(), ]) setActivities(acts) if (org) setOrgHeader(org) } catch (err) { setApiError('Fehler beim Laden der VVT-Daten. Bitte Verbindung prüfen.') console.error('VVT API load error:', err) } finally { setIsLoading(false) } } loadFromApi() }, []) // 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: 'export', label: 'Export & Compliance' }, ] if (isLoading) { return (
) } return (
{apiError && (
{apiError}
)} {/* Tab Navigation */}
{tabs.map(t => ( ))}
{/* Tab Content */} {tab === 'verzeichnis' && ( { setEditingId(id); setTab('editor') }} onNew={async () => { const vvtId = generateVVTId(activities.map(a => a.vvtId)) const newAct = createEmptyActivity(vvtId) try { const created = await apiCreateActivity(newAct) setActivities(prev => [...prev, created]) setEditingId(created.id) setTab('editor') } catch (err) { setApiError('Fehler beim Anlegen der Verarbeitung.') console.error(err) } }} onDelete={async (id) => { try { await apiDeleteActivity(id) setActivities(prev => prev.filter(a => a.id !== id)) } catch (err) { setApiError('Fehler beim Löschen der Verarbeitung.') console.error(err) } }} onAdoptGenerated={async (newActivities) => { const created: VVTActivity[] = [] for (const act of newActivities) { try { const saved = await apiCreateActivity(act) created.push(saved) } catch (err) { console.error('Failed to create activity from scope:', err) } } if (created.length > 0) setActivities(prev => [...prev, ...created]) }} /> )} {tab === 'editor' && ( { try { const saved = await apiUpdateActivity(updated.id, updated) setActivities(prev => prev.map(a => a.id === saved.id ? saved : a)) } catch (err) { setApiError('Fehler beim Speichern der Verarbeitung.') console.error(err) } }} onBack={() => setTab('verzeichnis')} onSelectActivity={(id) => setEditingId(id)} /> )} {tab === 'export' && ( { try { const saved = await apiUpsertOrganization(org) setOrgHeader(saved) } catch (err) { setApiError('Fehler beim Speichern der Organisationsdaten.') console.error(err) } }} /> )}
) }