This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/admin-v2/app/(admin)/compliance/vvt/page.tsx
BreakPilot Dev 660295e218 fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit
restores all missing components:

- Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education,
  infrastructure, communication, development, onboarding, rbac
- SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen,
  vendor-compliance, tom-generator, dsr, and more
- Developer portal (25 pages): API docs, SDK guides, frameworks
- All components, lib files, hooks, and types
- Updated package.json with all dependencies

The issue was caused by incomplete initial repository state - the full
admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2
but was never fully synced to the main admin-v2 directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 23:40:15 -08:00

335 lines
14 KiB
TypeScript

'use client'
/**
* VVT - Verarbeitungsverzeichnis
*
* Art. 30 DSGVO - Verzeichnis von Verarbeitungstaetigkeiten
*/
import { useState } from 'react'
import { PagePurpose } from '@/components/common/PagePurpose'
interface ProcessingActivity {
id: string
name: string
purpose: string
legalBasis: string
legalBasisDetail: string
categories: string[]
recipients: string[]
thirdCountryTransfer: boolean
thirdCountryDetails?: string
retentionPeriod: string
technicalMeasures: string[]
lastReview: string
status: 'active' | 'inactive' | 'review_needed'
}
export default function VVTPage() {
const [expandedActivity, setExpandedActivity] = useState<string | null>('user_accounts')
const [filterStatus, setFilterStatus] = useState<string>('all')
const processingActivities: ProcessingActivity[] = [
{
id: 'user_accounts',
name: 'Nutzerkontenverwaltung',
purpose: 'Bereitstellung und Verwaltung von Benutzerkonten fuer die Plattform-Nutzung',
legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO',
legalBasisDetail: 'Vertragserfuellung - Notwendig zur Bereitstellung des Dienstes',
categories: ['Name', 'E-Mail-Adresse', 'Passwort (gehasht)', 'Profilbild (optional)'],
recipients: ['Keine externen Empfaenger'],
thirdCountryTransfer: false,
retentionPeriod: '3 Jahre nach Kontolöschung',
technicalMeasures: ['Verschluesselung', 'Zugriffskontrolle', 'Audit-Logging'],
lastReview: '2024-12-01',
status: 'active'
},
{
id: 'consent_management',
name: 'Einwilligungsverwaltung',
purpose: 'Verwaltung und Dokumentation von Einwilligungen gemaess DSGVO',
legalBasis: 'Art. 6 Abs. 1 lit. c DSGVO',
legalBasisDetail: 'Rechtliche Verpflichtung - Nachweis der Einwilligung',
categories: ['Benutzer-ID', 'Einwilligungstyp', 'Zeitstempel', 'IP-Adresse', 'Version'],
recipients: ['DSB (Datenschutzbeauftragter)', 'Aufsichtsbehoerden bei Anfrage'],
thirdCountryTransfer: false,
retentionPeriod: '6 Jahre nach Widerruf (Nachweispflicht)',
technicalMeasures: ['Unveraenderbarkeit', 'Zeitstempel', 'Audit-Trail'],
lastReview: '2024-12-01',
status: 'active'
},
{
id: 'learning_analytics',
name: 'Lernfortschrittsanalyse',
purpose: 'Analyse des Lernfortschritts zur Verbesserung der Lernerfahrung',
legalBasis: 'Art. 6 Abs. 1 lit. a DSGVO',
legalBasisDetail: 'Einwilligung - Nutzer stimmt der Analyse explizit zu',
categories: ['Benutzer-ID', 'Lernaktivitaeten', 'Testergebnisse', 'Zeitaufwand'],
recipients: ['Lehrer (aggregiert)', 'Eltern (mit Einwilligung)'],
thirdCountryTransfer: false,
retentionPeriod: 'Bis zum Ende des Schuljahres + 1 Jahr',
technicalMeasures: ['Pseudonymisierung', 'Verschluesselung', 'Zugriffsbeschraenkung'],
lastReview: '2024-11-15',
status: 'active'
},
{
id: 'ai_processing',
name: 'KI-gestuetzte Verarbeitung',
purpose: 'Automatische Korrektur und Feedback-Generierung mittels KI',
legalBasis: 'Art. 6 Abs. 1 lit. a DSGVO',
legalBasisDetail: 'Einwilligung - Explizite Zustimmung zur KI-Verarbeitung',
categories: ['Benutzer-ID', 'Eingabetexte', 'Generierte Bewertungen'],
recipients: ['Ollama (lokal)', 'Optional: Cloud-LLM (mit Einwilligung)'],
thirdCountryTransfer: true,
thirdCountryDetails: 'OpenAI (USA) - nur bei expliziter Einwilligung, Standardvertragsklauseln',
retentionPeriod: 'Sofortige Loeschung nach Verarbeitung (keine Speicherung)',
technicalMeasures: ['Anonymisierung wo moeglich', 'Keine Speicherung bei Drittanbietern'],
lastReview: '2024-12-01',
status: 'review_needed'
},
{
id: 'support_requests',
name: 'Support-Anfragen',
purpose: 'Bearbeitung von Support- und Hilfe-Anfragen',
legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO',
legalBasisDetail: 'Vertragserfuellung - Teil des Service-Angebots',
categories: ['Name', 'E-Mail', 'Anfrage-Inhalt', 'Anhaenge'],
recipients: ['Support-Team', 'Entwickler (bei technischen Problemen)'],
thirdCountryTransfer: false,
retentionPeriod: '2 Jahre nach Abschluss des Tickets',
technicalMeasures: ['Zugriffskontrolle', 'Verschluesselung'],
lastReview: '2024-10-01',
status: 'active'
},
{
id: 'newsletter',
name: 'Newsletter-Versand',
purpose: 'Information ueber Updates, Features und relevante Bildungsthemen',
legalBasis: 'Art. 6 Abs. 1 lit. a DSGVO',
legalBasisDetail: 'Einwilligung - Double-Opt-In Verfahren',
categories: ['E-Mail-Adresse', 'Anrede', 'Praeferenzen'],
recipients: ['E-Mail-Provider (Mailpit/SMTP)'],
thirdCountryTransfer: false,
retentionPeriod: 'Bis zum Widerruf',
technicalMeasures: ['Abmelde-Link in jeder E-Mail', 'Verschluesselung'],
lastReview: '2024-11-01',
status: 'active'
},
{
id: 'logging',
name: 'System-Logging',
purpose: 'Sicherheit, Fehleranalyse und Betrieb der Plattform',
legalBasis: 'Art. 6 Abs. 1 lit. f DSGVO',
legalBasisDetail: 'Berechtigtes Interesse - Sicherheit und Betrieb',
categories: ['IP-Adresse', 'Zeitstempel', 'Anfrage-Details', 'User-Agent'],
recipients: ['IT-Administratoren', 'Bei Sicherheitsvorfaellen: Behoerden'],
thirdCountryTransfer: false,
retentionPeriod: '90 Tage (Standard-Logs), 2 Jahre (Security-Logs)',
technicalMeasures: ['IP-Anonymisierung nach 7 Tagen', 'Zugriffsbeschraenkung'],
lastReview: '2024-12-01',
status: 'active'
},
]
const getStatusBadge = (status: string) => {
switch (status) {
case 'active':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">Aktiv</span>
case 'inactive':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-600">Inaktiv</span>
case 'review_needed':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">Pruefung erforderlich</span>
default:
return null
}
}
const filteredActivities = filterStatus === 'all'
? processingActivities
: processingActivities.filter(a => a.status === filterStatus)
return (
<div>
<PagePurpose
title="Verarbeitungsverzeichnis (VVT)"
purpose="Verzeichnis aller Verarbeitungstaetigkeiten gemaess Art. 30 DSGVO. Dokumentiert Zweck, Rechtsgrundlage, Kategorien und Loeschfristen."
audience={['DSB', 'Auditoren', 'Aufsichtsbehoerden']}
gdprArticles={['Art. 30 (Verzeichnis von Verarbeitungstaetigkeiten)']}
architecture={{
services: ['consent-service (Go)', 'backend (Python)'],
databases: ['PostgreSQL'],
}}
relatedPages={[
{ name: 'DSMS', href: '/compliance/dsms', description: 'Datenschutz-Management-System' },
{ name: 'TOMs', href: '/compliance/tom', description: 'Technische Massnahmen' },
{ name: 'DSFA', href: '/compliance/dsfa', description: 'Datenschutz-Folgenabschaetzung' },
]}
collapsible={true}
defaultCollapsed={true}
/>
{/* Header */}
<div className="bg-white rounded-xl border border-slate-200 p-6 mb-6">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-lg font-semibold text-slate-900">Verarbeitungstaetigkeiten</h2>
<p className="text-sm text-slate-500 mt-1">{processingActivities.length} dokumentierte Taetigkeiten</p>
</div>
<button className="px-4 py-2 bg-purple-600 text-white rounded-lg text-sm font-medium hover:bg-purple-700">
+ Neue Taetigkeit
</button>
</div>
{/* Filter */}
<div className="flex gap-2">
{[
{ value: 'all', label: 'Alle' },
{ value: 'active', label: 'Aktiv' },
{ value: 'review_needed', label: 'Pruefung erforderlich' },
{ value: 'inactive', label: 'Inaktiv' },
].map(filter => (
<button
key={filter.value}
onClick={() => setFilterStatus(filter.value)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === filter.value
? 'bg-purple-600 text-white'
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
}`}
>
{filter.label}
</button>
))}
</div>
</div>
{/* Activities List */}
<div className="space-y-4">
{filteredActivities.map((activity) => (
<div key={activity.id} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<button
onClick={() => setExpandedActivity(expandedActivity === activity.id ? null : activity.id)}
className="w-full px-6 py-4 flex items-center justify-between hover:bg-slate-50 transition-colors"
>
<div className="flex items-center gap-4">
<div className="text-left">
<div className="flex items-center gap-3">
<h3 className="font-semibold text-slate-900">{activity.name}</h3>
{getStatusBadge(activity.status)}
{activity.thirdCountryTransfer && (
<span className="px-2 py-1 rounded-full text-xs font-medium bg-orange-100 text-orange-800">
Drittland-Transfer
</span>
)}
</div>
<p className="text-sm text-slate-500 mt-1">{activity.purpose}</p>
</div>
</div>
<svg
className={`w-5 h-5 text-slate-400 transition-transform ${expandedActivity === activity.id ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{expandedActivity === activity.id && (
<div className="px-6 pb-6 border-t border-slate-100">
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Left Column */}
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Rechtsgrundlage</h4>
<p className="font-semibold text-slate-900">{activity.legalBasis}</p>
<p className="text-sm text-slate-600">{activity.legalBasisDetail}</p>
</div>
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Datenkategorien</h4>
<div className="flex flex-wrap gap-2">
{activity.categories.map((cat, idx) => (
<span key={idx} className="px-2 py-1 bg-slate-100 text-slate-700 rounded text-sm">
{cat}
</span>
))}
</div>
</div>
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Empfaenger</h4>
<ul className="text-sm text-slate-700 list-disc list-inside">
{activity.recipients.map((rec, idx) => (
<li key={idx}>{rec}</li>
))}
</ul>
</div>
</div>
{/* Right Column */}
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Loeschfrist</h4>
<p className="text-slate-700">{activity.retentionPeriod}</p>
</div>
{activity.thirdCountryTransfer && activity.thirdCountryDetails && (
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Drittland-Transfer</h4>
<p className="text-slate-700">{activity.thirdCountryDetails}</p>
</div>
)}
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Technische Massnahmen</h4>
<div className="flex flex-wrap gap-2">
{activity.technicalMeasures.map((measure, idx) => (
<span key={idx} className="px-2 py-1 bg-green-100 text-green-700 rounded text-sm">
{measure}
</span>
))}
</div>
</div>
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Letzte Pruefung</h4>
<p className="text-slate-700">{activity.lastReview}</p>
</div>
</div>
</div>
<div className="mt-4 pt-4 border-t border-slate-100 flex gap-2">
<button className="px-3 py-1.5 text-sm text-purple-600 hover:text-purple-700 font-medium">
Bearbeiten
</button>
<button className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-700">
PDF exportieren
</button>
</div>
</div>
)}
</div>
))}
</div>
{/* Info */}
<div className="mt-6 bg-purple-50 border border-purple-200 rounded-xl p-4">
<div className="flex gap-3">
<svg className="w-5 h-5 text-purple-600 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<h4 className="font-semibold text-purple-900">Pflicht zur Fuehrung</h4>
<p className="text-sm text-purple-800 mt-1">
Gemaess Art. 30 DSGVO ist jeder Verantwortliche verpflichtet, ein Verzeichnis aller
Verarbeitungstaetigkeiten zu fuehren. Dieses Verzeichnis muss der Aufsichtsbehoerde
auf Anfrage zur Verfuegung gestellt werden.
</p>
</div>
</div>
</div>
</div>
)
}