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>
This commit is contained in:
334
admin-v2/app/(admin)/compliance/vvt/page.tsx
Normal file
334
admin-v2/app/(admin)/compliance/vvt/page.tsx
Normal file
@@ -0,0 +1,334 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user