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)/dsgvo/dsfa/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

738 lines
33 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
/**
* DSFA - Datenschutz-Folgenabschätzung
*
* Art. 35 DSGVO - Datenschutz-Folgenabschätzung
*
* Migriert auf SDK API: /sdk/v1/dsgvo/dsfa
*/
import { useState, useEffect } from 'react'
import { PagePurpose } from '@/components/common/PagePurpose'
interface DSFARisk {
id: string
category: string // confidentiality, integrity, availability, rights_freedoms
description: string
likelihood: string // low, medium, high
impact: string // low, medium, high
risk_level: string // low, medium, high, very_high
affected_data?: string[]
}
interface DSFAMitigation {
id: string
risk_id: string
description: string
type: string // technical, organizational, legal
status: string // planned, in_progress, implemented, verified
implemented_at?: string
residual_risk: string // low, medium, high
responsible_party: string
}
interface DSFA {
id: string
tenant_id: string
namespace_id?: string
processing_activity_id?: string
name: string
description: string
processing_description: string
necessity_assessment: string
proportionality_assessment: string
risks: DSFARisk[]
mitigations: DSFAMitigation[]
dpo_consulted: boolean
dpo_opinion?: string
authority_consulted: boolean
authority_reference?: string
status: string // draft, in_progress, completed, approved, rejected
overall_risk_level: string // low, medium, high, very_high
conclusion: string
created_at: string
updated_at: string
created_by: string
approved_by?: string
approved_at?: string
}
export default function DSFAPage() {
const [dsfas, setDsfas] = useState<DSFA[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [expandedProject, setExpandedProject] = useState<string | null>(null)
const [activeTab, setActiveTab] = useState<'projects' | 'methodology'>('projects')
const [showCreateModal, setShowCreateModal] = useState(false)
const [newDsfa, setNewDsfa] = useState({
name: '',
description: '',
processing_description: '',
necessity_assessment: '',
proportionality_assessment: '',
overall_risk_level: 'medium',
status: 'draft',
conclusion: ''
})
useEffect(() => {
loadDSFAs()
}, [])
async function loadDSFAs() {
setLoading(true)
setError(null)
try {
const res = await fetch('/sdk/v1/dsgvo/dsfa', {
headers: {
'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '',
'X-User-ID': localStorage.getItem('bp_user_id') || '',
}
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
const data = await res.json()
setDsfas(data.dsfas || [])
if ((data.dsfas || []).length > 0) {
setExpandedProject(data.dsfas[0].id)
}
} catch (err) {
console.error('Failed to load DSFAs:', err)
setError('Fehler beim Laden der DSFAs')
} finally {
setLoading(false)
}
}
async function createDSFA() {
try {
const res = await fetch('/sdk/v1/dsgvo/dsfa', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '',
'X-User-ID': localStorage.getItem('bp_user_id') || '',
},
body: JSON.stringify(newDsfa)
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
setShowCreateModal(false)
setNewDsfa({
name: '',
description: '',
processing_description: '',
necessity_assessment: '',
proportionality_assessment: '',
overall_risk_level: 'medium',
status: 'draft',
conclusion: ''
})
loadDSFAs()
} catch (err) {
console.error('Failed to create DSFA:', err)
alert('Fehler beim Erstellen der DSFA')
}
}
async function deleteDSFA(id: string) {
if (!confirm('DSFA wirklich löschen?')) return
try {
const res = await fetch(`/sdk/v1/dsgvo/dsfa/${id}`, {
method: 'DELETE',
headers: {
'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '',
'X-User-ID': localStorage.getItem('bp_user_id') || '',
}
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
loadDSFAs()
} catch (err) {
console.error('Failed to delete DSFA:', err)
alert('Fehler beim Löschen')
}
}
async function exportDSFA(id: string) {
try {
const res = await fetch(`/sdk/v1/dsgvo/dsfa/${id}/export`, {
headers: {
'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '',
'X-User-ID': localStorage.getItem('bp_user_id') || '',
}
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
const blob = await res.blob()
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `dsfa-export.json`
a.click()
window.URL.revokeObjectURL(url)
} catch (err) {
console.error('Export failed:', err)
alert('Export fehlgeschlagen')
}
}
const getStatusBadge = (status: string) => {
switch (status) {
case 'completed':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">Abgeschlossen</span>
case 'approved':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-emerald-100 text-emerald-800">Genehmigt</span>
case 'in_progress':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800">In Bearbeitung</span>
case 'draft':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-600">Entwurf</span>
case 'rejected':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800">Abgelehnt</span>
default:
return null
}
}
const getRiskBadge = (level: string) => {
switch (level) {
case 'very_high':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800">Sehr hoch</span>
case 'high':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-orange-100 text-orange-800">Hoch</span>
case 'medium':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">Mittel</span>
case 'low':
return <span className="px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">Niedrig</span>
default:
return null
}
}
const getCategoryLabel = (cat: string) => {
const labels: Record<string, string> = {
'confidentiality': 'Vertraulichkeit',
'integrity': 'Integrität',
'availability': 'Verfügbarkeit',
'rights_freedoms': 'Rechte der Betroffenen',
}
return labels[cat] || cat
}
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-slate-500">Lade DSFAs...</div>
</div>
)
}
return (
<div>
<PagePurpose
title="Datenschutz-Folgenabschätzung (DSFA)"
purpose="Systematische Risikoanalyse für Verarbeitungen mit hohem Risiko gemäß Art. 35 DSGVO. Dokumentiert Risiken, Maßnahmen und DSB-Freigaben."
audience={['DSB', 'Projektleiter', 'Entwickler', 'Geschäftsführung']}
gdprArticles={['Art. 35 (Datenschutz-Folgenabschätzung)', 'Art. 36 (Vorherige Konsultation)']}
architecture={{
services: ['AI Compliance SDK (Go)', 'PostgreSQL'],
databases: ['PostgreSQL'],
}}
relatedPages={[
{ name: 'VVT', href: '/dsgvo/vvt', description: 'Verarbeitungsverzeichnis' },
{ name: 'TOMs', href: '/dsgvo/tom', description: 'Technische Maßnahmen' },
{ name: 'DSR', href: '/dsgvo/dsr', description: 'Betroffenenrechte' },
]}
collapsible={true}
defaultCollapsed={true}
/>
{error && (
<div className="mb-6 bg-red-50 border border-red-200 rounded-xl p-4 text-red-700">
{error}
</div>
)}
{/* Tabs */}
<div className="flex items-center justify-between mb-6">
<div className="flex gap-2">
{[
{ id: 'projects', label: 'DSFA-Projekte' },
{ id: 'methodology', label: 'Methodik' },
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as typeof activeTab)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === tab.id
? 'bg-primary-600 text-white'
: 'bg-white text-slate-700 border border-slate-200 hover:bg-slate-50'
}`}
>
{tab.label}
</button>
))}
</div>
{activeTab === 'projects' && (
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 bg-primary-600 text-white rounded-lg text-sm font-medium hover:bg-primary-700"
>
+ Neue DSFA
</button>
)}
</div>
{/* Projects Tab */}
{activeTab === 'projects' && (
<div className="space-y-6">
{/* Statistics */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-xl border border-slate-200 p-4">
<div className="text-2xl font-bold text-slate-900">{dsfas.length}</div>
<div className="text-sm text-slate-500">DSFA-Projekte</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-4">
<div className="text-2xl font-bold text-green-600">
{dsfas.filter(d => d.status === 'completed' || d.status === 'approved').length}
</div>
<div className="text-sm text-slate-500">Abgeschlossen</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-4">
<div className="text-2xl font-bold text-blue-600">
{dsfas.filter(d => d.status === 'in_progress').length}
</div>
<div className="text-sm text-slate-500">In Bearbeitung</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-4">
<div className="text-2xl font-bold text-orange-600">
{dsfas.filter(d => d.overall_risk_level === 'high' || d.overall_risk_level === 'very_high').length}
</div>
<div className="text-sm text-slate-500">Hohes Risiko</div>
</div>
</div>
{/* DSFA List */}
{dsfas.length === 0 ? (
<div className="bg-white rounded-xl border border-slate-200 p-12 text-center">
<div className="text-slate-400 text-4xl mb-4"></div>
<h3 className="text-lg font-medium text-slate-800 mb-2">Keine DSFAs vorhanden</h3>
<p className="text-slate-500 mb-4">Erstellen Sie eine Datenschutz-Folgenabschätzung für Verarbeitungen mit hohem Risiko.</p>
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 bg-primary-600 text-white rounded-lg text-sm font-medium hover:bg-primary-700"
>
Erste DSFA erstellen
</button>
</div>
) : (
<div className="space-y-4">
{dsfas.map(dsfa => (
<div key={dsfa.id} className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<button
onClick={() => setExpandedProject(expandedProject === dsfa.id ? null : dsfa.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">{dsfa.name}</h3>
{getStatusBadge(dsfa.status)}
{getRiskBadge(dsfa.overall_risk_level)}
{dsfa.dpo_consulted && (
<span className="px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">
DSB-Konsultation
</span>
)}
</div>
<p className="text-sm text-slate-500 mt-1">{dsfa.description}</p>
</div>
</div>
<svg
className={`w-5 h-5 text-slate-400 transition-transform ${expandedProject === dsfa.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>
{expandedProject === dsfa.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: Assessments */}
<div className="space-y-4">
{dsfa.processing_description && (
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Verarbeitungsbeschreibung</h4>
<p className="text-sm text-slate-700 bg-slate-50 rounded-lg p-3">{dsfa.processing_description}</p>
</div>
)}
{dsfa.necessity_assessment && (
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Notwendigkeitsbewertung</h4>
<p className="text-sm text-slate-700 bg-slate-50 rounded-lg p-3">{dsfa.necessity_assessment}</p>
</div>
)}
{dsfa.conclusion && (
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Fazit</h4>
<p className="text-sm text-slate-700 bg-slate-50 rounded-lg p-3">{dsfa.conclusion}</p>
</div>
)}
</div>
{/* Right: Meta */}
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Erstellt</h4>
<p className="text-slate-700">{new Date(dsfa.created_at).toLocaleDateString('de-DE')}</p>
</div>
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Aktualisiert</h4>
<p className="text-slate-700">{new Date(dsfa.updated_at).toLocaleDateString('de-DE')}</p>
</div>
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">DSB-Konsultation</h4>
<p className={dsfa.dpo_consulted ? 'text-green-600 font-medium' : 'text-yellow-600'}>
{dsfa.dpo_consulted ? 'Ja' : 'Ausstehend'}
</p>
</div>
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">Aufsichtsbehörde</h4>
<p className={dsfa.authority_consulted ? 'text-green-600 font-medium' : 'text-slate-500'}>
{dsfa.authority_consulted ? 'Konsultiert' : 'Nicht konsultiert'}
</p>
</div>
</div>
{dsfa.dpo_opinion && (
<div>
<h4 className="text-sm font-medium text-slate-500 mb-1">DSB-Stellungnahme</h4>
<p className="text-sm text-slate-700 bg-slate-50 rounded-lg p-3">{dsfa.dpo_opinion}</p>
</div>
)}
</div>
</div>
{/* Risks */}
{dsfa.risks && dsfa.risks.length > 0 && (
<div className="mt-6">
<h4 className="text-sm font-medium text-slate-500 mb-3">Identifizierte Risiken</h4>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-slate-200">
<th className="text-left py-2 px-3 font-medium text-slate-500">Kategorie</th>
<th className="text-left py-2 px-3 font-medium text-slate-500">Beschreibung</th>
<th className="text-left py-2 px-3 font-medium text-slate-500">Risiko</th>
</tr>
</thead>
<tbody>
{dsfa.risks.map(risk => (
<tr key={risk.id} className="border-b border-slate-100">
<td className="py-2 px-3 font-medium text-slate-900">{getCategoryLabel(risk.category)}</td>
<td className="py-2 px-3 text-slate-600">{risk.description}</td>
<td className="py-2 px-3">{getRiskBadge(risk.risk_level)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Mitigations */}
{dsfa.mitigations && dsfa.mitigations.length > 0 && (
<div className="mt-4">
<h4 className="text-sm font-medium text-slate-500 mb-3">Maßnahmen</h4>
<div className="space-y-2">
{dsfa.mitigations.map(mit => (
<div key={mit.id} className="flex items-center justify-between p-3 bg-slate-50 rounded-lg">
<div>
<span className="text-sm text-slate-900">{mit.description}</span>
<div className="flex gap-2 mt-1">
<span className={`text-xs px-2 py-0.5 rounded ${
mit.type === 'technical' ? 'bg-blue-100 text-blue-700' :
mit.type === 'organizational' ? 'bg-purple-100 text-purple-700' :
'bg-slate-100 text-slate-600'
}`}>
{mit.type === 'technical' ? 'Technisch' : mit.type === 'organizational' ? 'Organisatorisch' : 'Rechtlich'}
</span>
{getStatusBadge(mit.status)}
</div>
</div>
<div className="text-right">
<div className="text-xs text-slate-500">Restrisiko</div>
{getRiskBadge(mit.residual_risk)}
</div>
</div>
))}
</div>
</div>
)}
<div className="mt-4 pt-4 border-t border-slate-100 flex gap-2">
<button
onClick={() => exportDSFA(dsfa.id)}
className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-700 border border-slate-300 rounded-lg"
>
Exportieren
</button>
<button
onClick={() => deleteDSFA(dsfa.id)}
className="px-3 py-1.5 text-sm text-red-600 hover:text-red-700 border border-red-300 rounded-lg"
>
Löschen
</button>
</div>
</div>
)}
</div>
))}
</div>
)}
</div>
)}
{/* Methodology Tab */}
{activeTab === 'methodology' && (
<div className="space-y-6">
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h2 className="text-lg font-semibold text-slate-900 mb-4">DSFA-Prozess nach Art. 35 DSGVO</h2>
<div className="space-y-6">
{[
{
step: 1,
title: 'Schwellwertanalyse',
description: 'Prüfung ob eine DSFA erforderlich ist anhand der Kriterien aus Art. 35 Abs. 3 und der DSK-Positivliste.',
details: ['Verarbeitung besonderer Kategorien (Art. 9)?', 'Systematisches Profiling?', 'Neue Technologien im Einsatz?', 'Daten von Minderjährigen?']
},
{
step: 2,
title: 'Beschreibung der Verarbeitung',
description: 'Systematische Beschreibung der geplanten Verarbeitungsvorgänge und Zwecke.',
details: ['Art, Umfang, Umstände der Verarbeitung', 'Zweck der Verarbeitung', 'Betroffene Personengruppen', 'Verantwortlichkeiten']
},
{
step: 3,
title: 'Notwendigkeit & Verhältnismäßigkeit',
description: 'Bewertung ob die Verarbeitung notwendig und verhältnismäßig ist.',
details: ['Rechtsgrundlage vorhanden?', 'Zweckbindung eingehalten?', 'Datenminimierung beachtet?', 'Speicherbegrenzung definiert?']
},
{
step: 4,
title: 'Risikobewertung',
description: 'Systematische Bewertung der Risiken für Rechte und Freiheiten der Betroffenen.',
details: ['Risiken identifizieren', 'Eintrittswahrscheinlichkeit bewerten', 'Schwere der Auswirkungen bewerten', 'Risiko-Score berechnen']
},
{
step: 5,
title: 'Abhilfemaßnahmen',
description: 'Definition von Maßnahmen zur Eindämmung der identifizierten Risiken.',
details: ['Technische Maßnahmen (TOMs)', 'Organisatorische Maßnahmen', 'Restrisiko-Bewertung', 'Implementierungsplan']
},
{
step: 6,
title: 'DSB-Konsultation',
description: 'Einholung der Stellungnahme des Datenschutzbeauftragten.',
details: ['DSFA dem DSB vorlegen', 'Stellungnahme dokumentieren', 'Ggf. Anpassungen vornehmen', 'Freigabe erteilen']
},
{
step: 7,
title: 'Vorherige Konsultation (Art. 36)',
description: 'Bei verbleibendem hohen Risiko: Konsultation der Aufsichtsbehörde.',
details: ['Nur bei hohem Restrisiko erforderlich', 'Aufsichtsbehörde hat 8 Wochen zur Prüfung', 'Dokumentation der Konsultation', 'Umsetzung der Auflagen']
}
].map(item => (
<div key={item.step} className="flex gap-4">
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary-100 text-primary-700 flex items-center justify-center font-bold">
{item.step}
</div>
<div className="flex-grow">
<h3 className="font-semibold text-slate-900">{item.title}</h3>
<p className="text-sm text-slate-600 mt-1">{item.description}</p>
<ul className="mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs text-slate-500">
{item.details.map((detail, idx) => (
<li key={idx} className="flex items-center gap-1">
<span className="text-primary-400"></span> {detail}
</li>
))}
</ul>
</div>
</div>
))}
</div>
</div>
{/* When is DSFA required */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h2 className="text-lg font-semibold text-slate-900 mb-4">Wann ist eine DSFA erforderlich?</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-3">
<h3 className="font-medium text-slate-700">Art. 35 Abs. 3 - Pflichtfälle:</h3>
<ul className="space-y-2 text-sm text-slate-600">
<li className="flex items-start gap-2">
<span className="text-red-500 mt-0.5"></span>
Systematische Bewertung persönlicher Aspekte (Profiling)
</li>
<li className="flex items-start gap-2">
<span className="text-red-500 mt-0.5"></span>
Umfangreiche Verarbeitung besonderer Kategorien (Art. 9)
</li>
<li className="flex items-start gap-2">
<span className="text-red-500 mt-0.5"></span>
Systematische Überwachung öffentlicher Bereiche
</li>
</ul>
</div>
<div className="space-y-3">
<h3 className="font-medium text-slate-700">Zusätzliche Kriterien (DSK-Liste):</h3>
<ul className="space-y-2 text-sm text-slate-600">
<li className="flex items-start gap-2">
<span className="text-orange-500 mt-0.5"></span>
Verarbeitung von Daten Minderjähriger
</li>
<li className="flex items-start gap-2">
<span className="text-orange-500 mt-0.5"></span>
Einsatz neuer Technologien (z.B. KI)
</li>
<li className="flex items-start gap-2">
<span className="text-orange-500 mt-0.5"></span>
Zusammenführung von Datensätzen
</li>
<li className="flex items-start gap-2">
<span className="text-orange-500 mt-0.5"></span>
Automatisierte Entscheidungsfindung
</li>
</ul>
</div>
</div>
</div>
</div>
)}
{/* Info */}
<div className="mt-6 bg-yellow-50 border border-yellow-200 rounded-xl p-4">
<div className="flex gap-3">
<svg className="w-5 h-5 text-yellow-600 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div>
<h4 className="font-semibold text-yellow-900">Wichtiger Hinweis</h4>
<p className="text-sm text-yellow-800 mt-1">
Eine DSFA ist <strong>vor</strong> Beginn der Verarbeitung durchzuführen. Bei wesentlichen Änderungen
an bestehenden Verarbeitungen muss die DSFA aktualisiert werden. Die Dokumentation muss
der Aufsichtsbehörde auf Anfrage vorgelegt werden können.
</p>
</div>
</div>
</div>
{/* Create Modal */}
{showCreateModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white rounded-xl shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
<div className="p-6 border-b border-slate-200">
<h3 className="text-lg font-semibold text-slate-900">Neue DSFA erstellen</h3>
</div>
<div className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Name *</label>
<input
type="text"
value={newDsfa.name}
onChange={(e) => setNewDsfa({ ...newDsfa, name: e.target.value })}
className="w-full border border-slate-300 rounded-lg px-3 py-2"
placeholder="z.B. KI-gestützte Korrektur und Bewertung"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Beschreibung *</label>
<textarea
value={newDsfa.description}
onChange={(e) => setNewDsfa({ ...newDsfa, description: e.target.value })}
className="w-full border border-slate-300 rounded-lg px-3 py-2 h-20"
placeholder="Kurze Beschreibung der zu bewertenden Verarbeitung..."
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Verarbeitungsbeschreibung</label>
<textarea
value={newDsfa.processing_description}
onChange={(e) => setNewDsfa({ ...newDsfa, processing_description: e.target.value })}
className="w-full border border-slate-300 rounded-lg px-3 py-2 h-24"
placeholder="Detaillierte Beschreibung der Verarbeitungsvorgänge..."
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Risikostufe</label>
<select
value={newDsfa.overall_risk_level}
onChange={(e) => setNewDsfa({ ...newDsfa, overall_risk_level: e.target.value })}
className="w-full border border-slate-300 rounded-lg px-3 py-2"
>
<option value="low">Niedrig</option>
<option value="medium">Mittel</option>
<option value="high">Hoch</option>
<option value="very_high">Sehr hoch</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Status</label>
<select
value={newDsfa.status}
onChange={(e) => setNewDsfa({ ...newDsfa, status: e.target.value })}
className="w-full border border-slate-300 rounded-lg px-3 py-2"
>
<option value="draft">Entwurf</option>
<option value="in_progress">In Bearbeitung</option>
<option value="completed">Abgeschlossen</option>
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">Notwendigkeitsbewertung</label>
<textarea
value={newDsfa.necessity_assessment}
onChange={(e) => setNewDsfa({ ...newDsfa, necessity_assessment: e.target.value })}
className="w-full border border-slate-300 rounded-lg px-3 py-2 h-20"
placeholder="Warum ist die Verarbeitung notwendig und verhältnismäßig?"
/>
</div>
</div>
<div className="p-6 border-t border-slate-200 flex justify-end gap-3">
<button
onClick={() => setShowCreateModal(false)}
className="px-4 py-2 border border-slate-300 rounded-lg text-sm font-medium text-slate-700 hover:bg-slate-50"
>
Abbrechen
</button>
<button
onClick={createDSFA}
disabled={!newDsfa.name || !newDsfa.description}
className="px-4 py-2 bg-primary-600 text-white rounded-lg text-sm font-medium hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
DSFA erstellen
</button>
</div>
</div>
</div>
)}
</div>
)
}