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>
738 lines
33 KiB
TypeScript
738 lines
33 KiB
TypeScript
'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>
|
||
)
|
||
}
|