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>
1424 lines
61 KiB
TypeScript
1424 lines
61 KiB
TypeScript
'use client'
|
||
|
||
/**
|
||
* Obligations Dashboard - Regulatory Obligations Overview
|
||
*
|
||
* Features:
|
||
* - Organization facts input for assessment
|
||
* - Applicable regulations based on sector/size
|
||
* - Obligations list with filtering (by regulation, deadline, responsible)
|
||
* - Incident reporting deadlines
|
||
* - Executive summary and PDF export
|
||
*/
|
||
|
||
import { useState, useEffect } from 'react'
|
||
import { PagePurpose } from '@/components/common/PagePurpose'
|
||
|
||
// Types matching the backend
|
||
interface Obligation {
|
||
id: string
|
||
regulation_id: string
|
||
title: string
|
||
description: string
|
||
legal_basis: { norm: string; article?: string }[]
|
||
category: string
|
||
responsible: string
|
||
deadline?: {
|
||
type: 'absolute' | 'relative'
|
||
date?: string
|
||
duration?: string
|
||
}
|
||
sanctions?: {
|
||
max_fine: string
|
||
personal_liability?: boolean
|
||
criminal?: boolean
|
||
}
|
||
evidence: string[]
|
||
priority: 'critical' | 'high' | 'medium' | 'low'
|
||
dependencies?: string[]
|
||
iso27001_mapping?: string[]
|
||
}
|
||
|
||
interface ApplicableRegulation {
|
||
id: string
|
||
name: string
|
||
classification: string
|
||
reason: string
|
||
obligation_count: number
|
||
}
|
||
|
||
interface IncidentDeadline {
|
||
regulation_id: string
|
||
phase: string
|
||
deadline: string
|
||
content: string
|
||
recipient: string
|
||
legal_basis: string
|
||
}
|
||
|
||
interface ExecutiveSummary {
|
||
total_regulations: number
|
||
total_obligations: number
|
||
critical_obligations: number
|
||
overdue_obligations: number
|
||
upcoming_deadlines: number
|
||
key_risks: string[]
|
||
recommended_actions: string[]
|
||
}
|
||
|
||
interface SanctionsSummary {
|
||
max_financial_risk: string
|
||
personal_liability_risk: boolean
|
||
criminal_risk: boolean
|
||
summary: string
|
||
}
|
||
|
||
interface ObligationsOverview {
|
||
id: string
|
||
organization_name: string
|
||
assessment_date: string
|
||
applicable_regulations: ApplicableRegulation[]
|
||
obligations: Obligation[]
|
||
incident_deadlines: IncidentDeadline[]
|
||
executive_summary: ExecutiveSummary
|
||
sanctions_summary: SanctionsSummary
|
||
}
|
||
|
||
// NIS2 Sector Options
|
||
const NIS2_SECTORS = {
|
||
annex_i: [
|
||
{ value: 'energy', label: 'Energie', description: 'Strom, Gas, Oel, Fernwaerme, Wasserstoff' },
|
||
{ value: 'transport', label: 'Verkehr', description: 'Luft, Schiene, Wasser, Strasse' },
|
||
{ value: 'banking', label: 'Bankwesen', description: 'Kreditinstitute' },
|
||
{ value: 'financial_markets', label: 'Finanzmarktinfrastruktur', description: 'Handelsplaetze, zentrale Gegenparteien' },
|
||
{ value: 'healthcare', label: 'Gesundheitswesen', description: 'Krankenhaeuser, Labore, Pharma' },
|
||
{ value: 'utilities', label: 'Trinkwasser', description: 'Trinkwasserversorgung' },
|
||
{ value: 'wastewater', label: 'Abwasser', description: 'Abwasserentsorgung' },
|
||
{ value: 'digital_infrastructure', label: 'Digitale Infrastruktur', description: 'IXP, DNS, TLD, Cloud, Rechenzentren' },
|
||
{ value: 'ict_services', label: 'ICT-Dienstleistungen', description: 'Managed Services, Managed Security' },
|
||
{ value: 'public_administration', label: 'Oeffentliche Verwaltung', description: 'Zentralregierung' },
|
||
{ value: 'space', label: 'Weltraum', description: 'Raumfahrtinfrastruktur' },
|
||
],
|
||
annex_ii: [
|
||
{ value: 'postal', label: 'Post- und Kurierdienste', description: 'Postdienstleister' },
|
||
{ value: 'waste_management', label: 'Abfallbewirtschaftung', description: 'Entsorgung, Recycling' },
|
||
{ value: 'chemicals', label: 'Chemie', description: 'Herstellung, Produktion, Vertrieb' },
|
||
{ value: 'food', label: 'Lebensmittel', description: 'Produktion, Verarbeitung, Grosshandel' },
|
||
{ value: 'manufacturing', label: 'Verarbeitendes Gewerbe', description: 'Medizinprodukte, IT, Fahrzeuge, Maschinen' },
|
||
{ value: 'digital_providers', label: 'Digitale Dienste', description: 'Online-Marktplaetze, Suchmaschinen, Social Media' },
|
||
{ value: 'research', label: 'Forschung', description: 'Forschungseinrichtungen' },
|
||
],
|
||
other: [
|
||
{ value: 'retail', label: 'Einzelhandel', description: 'Nicht NIS2-relevant' },
|
||
{ value: 'education', label: 'Bildung', description: 'Schulen, Universitaeten (ggf. oeffentlich)' },
|
||
{ value: 'other', label: 'Sonstige', description: 'Nicht in NIS2-Sektoren' },
|
||
],
|
||
}
|
||
|
||
// Size categories
|
||
const SIZE_CATEGORIES = [
|
||
{ value: 'micro', label: 'Kleinstunternehmen', description: '< 10 MA, < 2 Mio. EUR Umsatz' },
|
||
{ value: 'small', label: 'Kleines Unternehmen', description: '< 50 MA, < 10 Mio. EUR Umsatz' },
|
||
{ value: 'medium', label: 'Mittleres Unternehmen', description: '< 250 MA, < 50 Mio. EUR Umsatz' },
|
||
{ value: 'large', label: 'Grossunternehmen', description: '>= 250 MA oder >= 50 Mio. EUR Umsatz' },
|
||
]
|
||
|
||
// Special services (NIS2)
|
||
const SPECIAL_SERVICES = [
|
||
{ value: 'dns', label: 'DNS-Dienste', description: 'Authoritative oder rekursive DNS' },
|
||
{ value: 'tld', label: 'TLD-Registry', description: 'Top-Level-Domain Registrierung' },
|
||
{ value: 'cloud', label: 'Cloud-Computing', description: 'IaaS, PaaS, SaaS' },
|
||
{ value: 'datacenter', label: 'Rechenzentrum', description: 'Co-Location, Housing' },
|
||
{ value: 'cdn', label: 'CDN', description: 'Content Delivery Network' },
|
||
{ value: 'trust_services', label: 'Vertrauensdienste', description: 'QTSP, Zertifikate' },
|
||
{ value: 'msp', label: 'Managed Services', description: 'IT-Management fuer Dritte' },
|
||
{ value: 'mssp', label: 'Managed Security', description: 'Security Services fuer Dritte' },
|
||
]
|
||
|
||
// AI Act High-Risk Categories (Annex III)
|
||
const AI_HIGH_RISK_CATEGORIES = [
|
||
{ value: 'biometric', label: 'Biometrische Identifizierung', description: 'Gesichtserkennung, Fingerabdruck' },
|
||
{ value: 'critical_infrastructure', label: 'Kritische Infrastruktur', description: 'Wasser, Gas, Strom, Verkehr' },
|
||
{ value: 'education', label: 'Bildung & Ausbildung', description: 'Zulassung, Bewertung von Lernenden' },
|
||
{ value: 'employment', label: 'Beschaeftigung', description: 'Recruiting, Leistungsbewertung' },
|
||
{ value: 'public_services', label: 'Oeffentliche Dienste', description: 'Sozialleistungen, Kreditwuerdigkeit' },
|
||
{ value: 'law_enforcement', label: 'Strafverfolgung', description: 'Risikoeinschaetzung, Polygraph' },
|
||
{ value: 'migration', label: 'Migration & Asyl', description: 'Visumspruefung, Asylverfahren' },
|
||
{ value: 'justice', label: 'Justiz', description: 'Rechtsprechung, Streitbeilegung' },
|
||
]
|
||
|
||
// AI System Types
|
||
const AI_SYSTEM_TYPES = [
|
||
{ value: 'chatbot', label: 'Chatbot / Conversational AI', description: 'Kundendialog, Support' },
|
||
{ value: 'recommendation', label: 'Empfehlungssystem', description: 'Produkt-, Inhaltsempfehlungen' },
|
||
{ value: 'analytics', label: 'Predictive Analytics', description: 'Vorhersagen, Forecasting' },
|
||
{ value: 'document_processing', label: 'Dokumentenverarbeitung', description: 'OCR, NER, Klassifizierung' },
|
||
{ value: 'image_recognition', label: 'Bilderkennung', description: 'Objekterkennung, Qualitaetspruefung' },
|
||
{ value: 'voice_recognition', label: 'Spracherkennung', description: 'Speech-to-Text, Stimmanalyse' },
|
||
{ value: 'generative', label: 'Generative KI', description: 'Text-, Bild-, Code-Generierung' },
|
||
{ value: 'decision_support', label: 'Entscheidungsunterstuetzung', description: 'Automatisierte Entscheidungen' },
|
||
]
|
||
|
||
const PRIORITY_COLORS = {
|
||
critical: { bg: 'bg-red-100', text: 'text-red-800', border: 'border-red-300' },
|
||
high: { bg: 'bg-orange-100', text: 'text-orange-800', border: 'border-orange-300' },
|
||
medium: { bg: 'bg-yellow-100', text: 'text-yellow-800', border: 'border-yellow-300' },
|
||
low: { bg: 'bg-green-100', text: 'text-green-800', border: 'border-green-300' },
|
||
}
|
||
|
||
const CATEGORY_ICONS: Record<string, string> = {
|
||
meldepflicht: '📋',
|
||
governance: '👔',
|
||
technisch: '🔧',
|
||
personal: '👥',
|
||
lieferkette: '🔗',
|
||
audit: '📊',
|
||
}
|
||
|
||
export default function ObligationsPage() {
|
||
// Assessment input state
|
||
const [organizationName, setOrganizationName] = useState('')
|
||
const [sector, setSector] = useState('')
|
||
const [sizeCategory, setSizeCategory] = useState('')
|
||
const [specialServices, setSpecialServices] = useState<string[]>([])
|
||
const [isKritis, setIsKritis] = useState(false)
|
||
const [isPartOfGroup, setIsPartOfGroup] = useState(false)
|
||
const [processesPersonalData, setProcessesPersonalData] = useState(true)
|
||
const [usesAI, setUsesAI] = useState(false)
|
||
const [country, setCountry] = useState('DE')
|
||
|
||
// AI Act specific state
|
||
const [aiHighRiskCategories, setAiHighRiskCategories] = useState<string[]>([])
|
||
const [aiSystemTypes, setAiSystemTypes] = useState<string[]>([])
|
||
const [isGPAIProvider, setIsGPAIProvider] = useState(false)
|
||
const [aiUsesPublicSpaces, setAiUsesPublicSpaces] = useState(false)
|
||
const [aiAffectsEmployees, setAiAffectsEmployees] = useState(false)
|
||
|
||
// Export state
|
||
const [exporting, setExporting] = useState(false)
|
||
const [exportFormat, setExportFormat] = useState<'pdf' | 'markdown'>('pdf')
|
||
|
||
// Assessment result state
|
||
const [overview, setOverview] = useState<ObligationsOverview | null>(null)
|
||
const [loading, setLoading] = useState(false)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
// Filter state
|
||
const [filterRegulation, setFilterRegulation] = useState<string>('all')
|
||
const [filterPriority, setFilterPriority] = useState<string>('all')
|
||
const [filterResponsible, setFilterResponsible] = useState<string>('all')
|
||
const [activeTab, setActiveTab] = useState<'all' | 'by-deadline' | 'by-regulation' | 'by-responsible'>('all')
|
||
|
||
// Check for stored assessment
|
||
useEffect(() => {
|
||
const stored = localStorage.getItem('obligations_assessment')
|
||
if (stored) {
|
||
try {
|
||
setOverview(JSON.parse(stored))
|
||
} catch (e) {
|
||
console.error('Failed to parse stored assessment:', e)
|
||
}
|
||
}
|
||
}, [])
|
||
|
||
const runAssessment = async () => {
|
||
setLoading(true)
|
||
setError(null)
|
||
|
||
// Build facts from inputs
|
||
const facts = {
|
||
organization: {
|
||
employee_count: sizeCategory === 'micro' ? 5 : sizeCategory === 'small' ? 30 : sizeCategory === 'medium' ? 150 : 500,
|
||
annual_revenue: sizeCategory === 'micro' ? 1000000 : sizeCategory === 'small' ? 5000000 : sizeCategory === 'medium' ? 30000000 : 100000000,
|
||
country: country,
|
||
is_part_of_group: isPartOfGroup,
|
||
eu_member: ['DE', 'AT', 'FR', 'IT', 'ES', 'NL', 'BE', 'PL'].includes(country),
|
||
},
|
||
sector: {
|
||
primary_sector: sector,
|
||
special_services: specialServices,
|
||
is_kritis: isKritis,
|
||
kritis_threshold_met: isKritis,
|
||
},
|
||
data_protection: {
|
||
processes_personal_data: processesPersonalData,
|
||
},
|
||
ai_usage: {
|
||
uses_ai: usesAI,
|
||
high_risk_categories: aiHighRiskCategories,
|
||
system_types: aiSystemTypes,
|
||
is_gpai_provider: isGPAIProvider,
|
||
public_spaces_biometric: aiUsesPublicSpaces,
|
||
affects_employees: aiAffectsEmployees,
|
||
},
|
||
}
|
||
|
||
try {
|
||
// Try to call the SDK backend
|
||
const res = await fetch('/api/sdk/v1/ucca/obligations/assess', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
facts,
|
||
organization_name: organizationName,
|
||
}),
|
||
})
|
||
|
||
if (res.ok) {
|
||
const data = await res.json()
|
||
setOverview(data.overview)
|
||
localStorage.setItem('obligations_assessment', JSON.stringify(data.overview))
|
||
} else {
|
||
// Fallback: Generate mock data for demo
|
||
const mockOverview = generateMockOverview(organizationName, sector, sizeCategory, specialServices, isKritis, usesAI, aiHighRiskCategories)
|
||
setOverview(mockOverview)
|
||
localStorage.setItem('obligations_assessment', JSON.stringify(mockOverview))
|
||
}
|
||
} catch (err) {
|
||
console.error('Assessment failed:', err)
|
||
// Generate mock data for demo
|
||
const mockOverview = generateMockOverview(organizationName, sector, sizeCategory, specialServices, isKritis, usesAI, aiHighRiskCategories)
|
||
setOverview(mockOverview)
|
||
localStorage.setItem('obligations_assessment', JSON.stringify(mockOverview))
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const clearAssessment = () => {
|
||
setOverview(null)
|
||
localStorage.removeItem('obligations_assessment')
|
||
}
|
||
|
||
const exportMemo = async () => {
|
||
if (!overview) return
|
||
setExporting(true)
|
||
|
||
try {
|
||
// Try SDK export endpoint first
|
||
const res = await fetch('/api/sdk/v1/ucca/obligations/export/direct', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
overview: overview,
|
||
format: exportFormat,
|
||
language: 'de',
|
||
}),
|
||
})
|
||
|
||
if (res.ok) {
|
||
const data = await res.json()
|
||
|
||
if (exportFormat === 'pdf' && data.content) {
|
||
// Decode base64 and download PDF
|
||
const byteCharacters = atob(data.content)
|
||
const byteNumbers = new Array(byteCharacters.length)
|
||
for (let i = 0; i < byteCharacters.length; i++) {
|
||
byteNumbers[i] = byteCharacters.charCodeAt(i)
|
||
}
|
||
const byteArray = new Uint8Array(byteNumbers)
|
||
const blob = new Blob([byteArray], { type: 'application/pdf' })
|
||
const url = URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = data.filename || `pflichten-memo-${new Date().toISOString().split('T')[0]}.pdf`
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
document.body.removeChild(a)
|
||
URL.revokeObjectURL(url)
|
||
setExporting(false)
|
||
return
|
||
} else if (data.content) {
|
||
// Download markdown
|
||
const blob = new Blob([data.content], { type: 'text/markdown;charset=utf-8' })
|
||
const url = URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = data.filename || `pflichten-memo-${new Date().toISOString().split('T')[0]}.md`
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
document.body.removeChild(a)
|
||
URL.revokeObjectURL(url)
|
||
setExporting(false)
|
||
return
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('SDK export failed, falling back to local export:', err)
|
||
}
|
||
|
||
// Fallback: Generate markdown locally
|
||
let content = `# Pflichten-Uebersicht fuer die Geschaeftsfuehrung\n\n`
|
||
content += `**Datum:** ${new Date(overview.assessment_date).toLocaleDateString('de-DE')}\n`
|
||
content += `**Organisation:** ${overview.organization_name || 'Nicht angegeben'}\n\n`
|
||
content += `---\n\n`
|
||
|
||
content += `## Executive Summary\n\n`
|
||
content += `| Kennzahl | Wert |\n`
|
||
content += `|----------|------|\n`
|
||
content += `| Anwendbare Regulierungen | ${overview.executive_summary.total_regulations} |\n`
|
||
content += `| Gesamtzahl Pflichten | ${overview.executive_summary.total_obligations} |\n`
|
||
content += `| Kritische Pflichten | ${overview.executive_summary.critical_obligations} |\n`
|
||
content += `| Ueberfaellige Pflichten | ${overview.executive_summary.overdue_obligations} |\n\n`
|
||
|
||
if (overview.executive_summary.key_risks.length > 0) {
|
||
content += `### Hauptrisiken\n\n`
|
||
overview.executive_summary.key_risks.forEach(risk => {
|
||
content += `- ${risk}\n`
|
||
})
|
||
content += `\n`
|
||
}
|
||
|
||
if (overview.executive_summary.recommended_actions.length > 0) {
|
||
content += `### Empfohlene Massnahmen\n\n`
|
||
overview.executive_summary.recommended_actions.forEach((action, i) => {
|
||
content += `${i + 1}. ${action}\n`
|
||
})
|
||
content += `\n`
|
||
}
|
||
|
||
content += `## Anwendbare Regulierungen\n\n`
|
||
overview.applicable_regulations.forEach(reg => {
|
||
content += `### ${reg.name}\n`
|
||
content += `- **Klassifizierung:** ${reg.classification}\n`
|
||
content += `- **Begruendung:** ${reg.reason}\n`
|
||
content += `- **Anzahl Pflichten:** ${reg.obligation_count}\n\n`
|
||
})
|
||
|
||
content += `## Sanktionsrisiken\n\n`
|
||
content += `${overview.sanctions_summary.summary}\n\n`
|
||
if (overview.sanctions_summary.max_financial_risk) {
|
||
content += `- **Maximales Bussgeld:** ${overview.sanctions_summary.max_financial_risk}\n`
|
||
}
|
||
if (overview.sanctions_summary.personal_liability_risk) {
|
||
content += `- **Persoenliche Haftung:** Ja\n`
|
||
}
|
||
content += `\n`
|
||
|
||
content += `## Kritische Pflichten\n\n`
|
||
overview.obligations
|
||
.filter(o => o.priority === 'critical')
|
||
.forEach(obl => {
|
||
content += `### ${obl.id}: ${obl.title}\n\n`
|
||
content += `${obl.description}\n\n`
|
||
content += `- **Verantwortlich:** ${obl.responsible}\n`
|
||
if (obl.deadline) {
|
||
content += `- **Frist:** ${obl.deadline.date || obl.deadline.duration}\n`
|
||
}
|
||
content += `\n`
|
||
})
|
||
|
||
if (overview.incident_deadlines.length > 0) {
|
||
content += `## Meldepflichten bei Sicherheitsvorfaellen\n\n`
|
||
content += `| Phase | Frist | Empfaenger |\n`
|
||
content += `|-------|-------|------------|\n`
|
||
overview.incident_deadlines.forEach(d => {
|
||
content += `| ${d.phase} | ${d.deadline} | ${d.recipient} |\n`
|
||
})
|
||
content += `\n`
|
||
}
|
||
|
||
content += `---\n\n`
|
||
content += `*Dieses Dokument wurde automatisch generiert und ersetzt keine Rechtsberatung.*\n`
|
||
|
||
// Download as markdown
|
||
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' })
|
||
const url = URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = `pflichten-memo-${new Date().toISOString().split('T')[0]}.md`
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
document.body.removeChild(a)
|
||
URL.revokeObjectURL(url)
|
||
setExporting(false)
|
||
}
|
||
|
||
// Filter obligations
|
||
const filteredObligations = overview?.obligations.filter(o => {
|
||
if (filterRegulation !== 'all' && o.regulation_id !== filterRegulation) return false
|
||
if (filterPriority !== 'all' && o.priority !== filterPriority) return false
|
||
if (filterResponsible !== 'all' && o.responsible !== filterResponsible) return false
|
||
return true
|
||
}) || []
|
||
|
||
// Group obligations
|
||
const obligationsByRegulation = overview?.obligations.reduce((acc, o) => {
|
||
if (!acc[o.regulation_id]) acc[o.regulation_id] = []
|
||
acc[o.regulation_id].push(o)
|
||
return acc
|
||
}, {} as Record<string, Obligation[]>) || {}
|
||
|
||
const obligationsByResponsible = overview?.obligations.reduce((acc, o) => {
|
||
if (!acc[o.responsible]) acc[o.responsible] = []
|
||
acc[o.responsible].push(o)
|
||
return acc
|
||
}, {} as Record<string, Obligation[]>) || {}
|
||
|
||
// Get unique values for filters
|
||
const uniqueRegulations = [...new Set(overview?.obligations.map(o => o.regulation_id) || [])]
|
||
const uniqueResponsibles = [...new Set(overview?.obligations.map(o => o.responsible) || [])]
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<PagePurpose
|
||
title="Pflichten-Uebersicht"
|
||
purpose="Aggregierte Uebersicht aller regulatorischen Pflichten aus NIS2, DSGVO, AI Act und weiteren Vorschriften. Basierend auf Ihren Unternehmensdaten werden automatisch anwendbare Pflichten, Fristen und Sanktionen ermittelt."
|
||
audience={['Geschaeftsfuehrung', 'DSB', 'CISO', 'Compliance Officer']}
|
||
gdprArticles={['NIS2 Art. 21', 'BSIG-E § 30-33', 'Art. 5 DSGVO']}
|
||
architecture={{
|
||
services: ['ai-compliance-sdk (Go)'],
|
||
databases: ['PostgreSQL'],
|
||
}}
|
||
relatedPages={[
|
||
{ name: 'Compliance Hub', href: '/compliance/hub', description: 'Zentrale Uebersicht' },
|
||
{ name: 'Controls', href: '/compliance/controls', description: 'Technische Massnahmen' },
|
||
{ name: 'AI Act', href: '/compliance/ai-act', description: 'KI-Risikoklassifizierung' },
|
||
]}
|
||
/>
|
||
|
||
{/* Assessment Form or Results */}
|
||
{!overview ? (
|
||
<div className="bg-white rounded-xl shadow-sm border p-6">
|
||
<h2 className="text-lg font-semibold text-slate-900 mb-4">Pflichten-Assessment starten</h2>
|
||
<p className="text-sm text-slate-600 mb-6">
|
||
Geben Sie Ihre Unternehmensdaten ein, um automatisch zu ermitteln, welche regulatorischen Pflichten fuer Sie gelten.
|
||
</p>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{/* Organization Name */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||
Organisationsname (optional)
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={organizationName}
|
||
onChange={(e) => setOrganizationName(e.target.value)}
|
||
placeholder="z.B. Muster GmbH"
|
||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
||
/>
|
||
</div>
|
||
|
||
{/* Country */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||
Land
|
||
</label>
|
||
<select
|
||
value={country}
|
||
onChange={(e) => setCountry(e.target.value)}
|
||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
||
>
|
||
<option value="DE">Deutschland</option>
|
||
<option value="AT">Oesterreich</option>
|
||
<option value="CH">Schweiz</option>
|
||
<option value="FR">Frankreich</option>
|
||
<option value="IT">Italien</option>
|
||
<option value="NL">Niederlande</option>
|
||
</select>
|
||
</div>
|
||
|
||
{/* Sector */}
|
||
<div className="md:col-span-2">
|
||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||
Branche/Sektor
|
||
</label>
|
||
<select
|
||
value={sector}
|
||
onChange={(e) => setSector(e.target.value)}
|
||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
||
>
|
||
<option value="">Bitte waehlen...</option>
|
||
<optgroup label="NIS2 Anhang I (Besonders wichtig)">
|
||
{NIS2_SECTORS.annex_i.map(s => (
|
||
<option key={s.value} value={s.value}>{s.label} - {s.description}</option>
|
||
))}
|
||
</optgroup>
|
||
<optgroup label="NIS2 Anhang II (Wichtig)">
|
||
{NIS2_SECTORS.annex_ii.map(s => (
|
||
<option key={s.value} value={s.value}>{s.label} - {s.description}</option>
|
||
))}
|
||
</optgroup>
|
||
<optgroup label="Sonstige">
|
||
{NIS2_SECTORS.other.map(s => (
|
||
<option key={s.value} value={s.value}>{s.label} - {s.description}</option>
|
||
))}
|
||
</optgroup>
|
||
</select>
|
||
</div>
|
||
|
||
{/* Size Category */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||
Unternehmensgroesse
|
||
</label>
|
||
<select
|
||
value={sizeCategory}
|
||
onChange={(e) => setSizeCategory(e.target.value)}
|
||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
||
>
|
||
<option value="">Bitte waehlen...</option>
|
||
{SIZE_CATEGORIES.map(s => (
|
||
<option key={s.value} value={s.value}>{s.label} - {s.description}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
{/* Part of Group */}
|
||
<div className="flex items-center gap-4">
|
||
<label className="flex items-center gap-2 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={isPartOfGroup}
|
||
onChange={(e) => setIsPartOfGroup(e.target.checked)}
|
||
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500"
|
||
/>
|
||
<span className="text-sm text-slate-700">Teil eines Konzerns</span>
|
||
</label>
|
||
<label className="flex items-center gap-2 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={isKritis}
|
||
onChange={(e) => setIsKritis(e.target.checked)}
|
||
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500"
|
||
/>
|
||
<span className="text-sm text-slate-700">KRITIS-Betreiber</span>
|
||
</label>
|
||
</div>
|
||
|
||
{/* Special Services */}
|
||
<div className="md:col-span-2">
|
||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||
Besondere Dienste (NIS2 Annex I)
|
||
</label>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||
{SPECIAL_SERVICES.map(service => (
|
||
<label key={service.value} className="flex items-center gap-2 cursor-pointer p-2 rounded-lg border border-slate-200 hover:bg-slate-50">
|
||
<input
|
||
type="checkbox"
|
||
checked={specialServices.includes(service.value)}
|
||
onChange={(e) => {
|
||
if (e.target.checked) {
|
||
setSpecialServices([...specialServices, service.value])
|
||
} else {
|
||
setSpecialServices(specialServices.filter(s => s !== service.value))
|
||
}
|
||
}}
|
||
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500"
|
||
/>
|
||
<span className="text-sm text-slate-700">{service.label}</span>
|
||
</label>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Additional flags */}
|
||
<div className="md:col-span-2 flex flex-wrap gap-6">
|
||
<label className="flex items-center gap-2 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={processesPersonalData}
|
||
onChange={(e) => setProcessesPersonalData(e.target.checked)}
|
||
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500"
|
||
/>
|
||
<span className="text-sm text-slate-700">Verarbeitet personenbezogene Daten (DSGVO)</span>
|
||
</label>
|
||
<label className="flex items-center gap-2 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={usesAI}
|
||
onChange={(e) => setUsesAI(e.target.checked)}
|
||
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500"
|
||
/>
|
||
<span className="text-sm text-slate-700">Setzt KI-Systeme ein (AI Act)</span>
|
||
</label>
|
||
</div>
|
||
|
||
{/* AI Act Specific Questions - shown when usesAI is true */}
|
||
{usesAI && (
|
||
<>
|
||
<div className="md:col-span-2 border-t pt-6 mt-2">
|
||
<h3 className="text-md font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
||
<span className="text-xl">🤖</span> AI Act Details
|
||
</h3>
|
||
|
||
{/* AI System Types */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||
Welche KI-Systeme setzen Sie ein?
|
||
</label>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||
{AI_SYSTEM_TYPES.map(type => (
|
||
<label key={type.value} className="flex items-center gap-2 cursor-pointer p-2 rounded-lg border border-slate-200 hover:bg-slate-50">
|
||
<input
|
||
type="checkbox"
|
||
checked={aiSystemTypes.includes(type.value)}
|
||
onChange={(e) => {
|
||
if (e.target.checked) {
|
||
setAiSystemTypes([...aiSystemTypes, type.value])
|
||
} else {
|
||
setAiSystemTypes(aiSystemTypes.filter(s => s !== type.value))
|
||
}
|
||
}}
|
||
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500"
|
||
/>
|
||
<div>
|
||
<span className="text-sm text-slate-700">{type.label}</span>
|
||
<p className="text-xs text-slate-500">{type.description}</p>
|
||
</div>
|
||
</label>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* High-Risk Categories */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||
In welchen Hochrisiko-Bereichen setzen Sie KI ein? (Annex III AI Act)
|
||
</label>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||
{AI_HIGH_RISK_CATEGORIES.map(cat => (
|
||
<label key={cat.value} className="flex items-center gap-2 cursor-pointer p-2 rounded-lg border border-slate-200 hover:bg-slate-50">
|
||
<input
|
||
type="checkbox"
|
||
checked={aiHighRiskCategories.includes(cat.value)}
|
||
onChange={(e) => {
|
||
if (e.target.checked) {
|
||
setAiHighRiskCategories([...aiHighRiskCategories, cat.value])
|
||
} else {
|
||
setAiHighRiskCategories(aiHighRiskCategories.filter(s => s !== cat.value))
|
||
}
|
||
}}
|
||
className="w-4 h-4 text-orange-600 rounded focus:ring-orange-500"
|
||
/>
|
||
<div>
|
||
<span className="text-sm text-slate-700">{cat.label}</span>
|
||
<p className="text-xs text-slate-500">{cat.description}</p>
|
||
</div>
|
||
</label>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Additional AI flags */}
|
||
<div className="flex flex-wrap gap-6">
|
||
<label className="flex items-center gap-2 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={isGPAIProvider}
|
||
onChange={(e) => setIsGPAIProvider(e.target.checked)}
|
||
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500"
|
||
/>
|
||
<span className="text-sm text-slate-700">Anbieter von General-Purpose AI (z.B. LLM)</span>
|
||
</label>
|
||
<label className="flex items-center gap-2 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={aiUsesPublicSpaces}
|
||
onChange={(e) => setAiUsesPublicSpaces(e.target.checked)}
|
||
className="w-4 h-4 text-red-600 rounded focus:ring-red-500"
|
||
/>
|
||
<span className="text-sm text-slate-700">Biometrische Identifizierung im oeffentlichen Raum</span>
|
||
</label>
|
||
<label className="flex items-center gap-2 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={aiAffectsEmployees}
|
||
onChange={(e) => setAiAffectsEmployees(e.target.checked)}
|
||
className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500"
|
||
/>
|
||
<span className="text-sm text-slate-700">KI-Entscheidungen betreffen Mitarbeiter</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
{/* Submit Button */}
|
||
<div className="mt-6">
|
||
<button
|
||
onClick={runAssessment}
|
||
disabled={loading || !sector || !sizeCategory}
|
||
className="px-6 py-3 bg-purple-600 text-white rounded-lg font-medium hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
{loading ? (
|
||
<span className="flex items-center gap-2">
|
||
<svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
|
||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||
</svg>
|
||
Analysiere...
|
||
</span>
|
||
) : (
|
||
'Pflichten ermitteln'
|
||
)}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<>
|
||
{/* Results Header */}
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h2 className="text-lg font-semibold text-slate-900">
|
||
Pflichten-Assessment: {overview.organization_name || 'Unbenannt'}
|
||
</h2>
|
||
<p className="text-sm text-slate-500">
|
||
Erstellt am {new Date(overview.assessment_date).toLocaleDateString('de-DE')}
|
||
</p>
|
||
</div>
|
||
<div className="flex gap-2 items-center">
|
||
{/* Export Format Toggle */}
|
||
<div className="flex rounded-lg border border-slate-300 overflow-hidden">
|
||
<button
|
||
onClick={() => setExportFormat('pdf')}
|
||
className={`px-3 py-2 text-sm ${exportFormat === 'pdf' ? 'bg-purple-600 text-white' : 'bg-white text-slate-600 hover:bg-slate-50'}`}
|
||
>
|
||
PDF
|
||
</button>
|
||
<button
|
||
onClick={() => setExportFormat('markdown')}
|
||
className={`px-3 py-2 text-sm ${exportFormat === 'markdown' ? 'bg-purple-600 text-white' : 'bg-white text-slate-600 hover:bg-slate-50'}`}
|
||
>
|
||
Markdown
|
||
</button>
|
||
</div>
|
||
<button
|
||
onClick={exportMemo}
|
||
disabled={exporting}
|
||
className="px-4 py-2 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||
>
|
||
{exporting ? (
|
||
<>
|
||
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
|
||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||
</svg>
|
||
Exportiere...
|
||
</>
|
||
) : (
|
||
<>
|
||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||
</svg>
|
||
Exportieren
|
||
</>
|
||
)}
|
||
</button>
|
||
<button
|
||
onClick={clearAssessment}
|
||
className="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg text-sm hover:bg-slate-200"
|
||
>
|
||
Neues Assessment
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Executive Summary Cards */}
|
||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||
<div className="bg-white rounded-xl shadow-sm border p-4">
|
||
<p className="text-sm text-slate-500">Regulierungen</p>
|
||
<p className="text-3xl font-bold text-blue-600">{overview.executive_summary.total_regulations}</p>
|
||
<p className="text-xs text-slate-500">anwendbar</p>
|
||
</div>
|
||
<div className="bg-white rounded-xl shadow-sm border p-4">
|
||
<p className="text-sm text-slate-500">Pflichten</p>
|
||
<p className="text-3xl font-bold text-purple-600">{overview.executive_summary.total_obligations}</p>
|
||
<p className="text-xs text-slate-500">gesamt</p>
|
||
</div>
|
||
<div className="bg-white rounded-xl shadow-sm border p-4">
|
||
<p className="text-sm text-slate-500">Kritisch</p>
|
||
<p className="text-3xl font-bold text-red-600">{overview.executive_summary.critical_obligations}</p>
|
||
<p className="text-xs text-slate-500">hoechste Prioritaet</p>
|
||
</div>
|
||
<div className="bg-white rounded-xl shadow-sm border p-4">
|
||
<p className="text-sm text-slate-500">Ueberfaellig</p>
|
||
<p className="text-3xl font-bold text-orange-600">{overview.executive_summary.overdue_obligations}</p>
|
||
<p className="text-xs text-slate-500">sofort handeln</p>
|
||
</div>
|
||
<div className="bg-white rounded-xl shadow-sm border p-4">
|
||
<p className="text-sm text-slate-500">Max. Risiko</p>
|
||
<p className="text-xl font-bold text-red-600">{overview.sanctions_summary.max_financial_risk}</p>
|
||
<p className="text-xs text-slate-500">
|
||
{overview.sanctions_summary.personal_liability_risk ? '+ pers. Haftung' : ''}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Applicable Regulations */}
|
||
<div className="bg-white rounded-xl shadow-sm border p-6">
|
||
<h3 className="text-lg font-semibold text-slate-900 mb-4">Anwendbare Regulierungen</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{overview.applicable_regulations.map(reg => (
|
||
<div key={reg.id} className="p-4 rounded-lg border border-slate-200 bg-slate-50">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<span className="font-medium text-slate-900">{reg.name}</span>
|
||
<span className="px-2 py-1 bg-purple-100 text-purple-700 rounded-full text-xs">
|
||
{reg.obligation_count} Pflichten
|
||
</span>
|
||
</div>
|
||
<p className="text-sm text-slate-600 mb-2">
|
||
<span className="font-medium">Klassifizierung:</span> {reg.classification}
|
||
</p>
|
||
<p className="text-xs text-slate-500">{reg.reason}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Incident Deadlines */}
|
||
{overview.incident_deadlines.length > 0 && (
|
||
<div className="bg-white rounded-xl shadow-sm border p-6">
|
||
<h3 className="text-lg font-semibold text-slate-900 mb-4">Meldepflichten bei Sicherheitsvorfaellen</h3>
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead className="bg-slate-50">
|
||
<tr>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Phase</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Frist</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Empfaenger</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Inhalt</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-slate-200">
|
||
{overview.incident_deadlines.map((deadline, i) => (
|
||
<tr key={i} className="hover:bg-slate-50">
|
||
<td className="px-4 py-3 font-medium text-slate-900">{deadline.phase}</td>
|
||
<td className="px-4 py-3">
|
||
<span className="px-2 py-1 bg-red-100 text-red-700 rounded-full text-sm font-medium">
|
||
{deadline.deadline}
|
||
</span>
|
||
</td>
|
||
<td className="px-4 py-3 text-slate-600">{deadline.recipient}</td>
|
||
<td className="px-4 py-3 text-sm text-slate-500">{deadline.content}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Obligations List with Filters */}
|
||
<div className="bg-white rounded-xl shadow-sm border">
|
||
<div className="p-4 border-b">
|
||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||
<h3 className="text-lg font-semibold text-slate-900">Pflichten ({filteredObligations.length})</h3>
|
||
<div className="flex flex-wrap gap-2">
|
||
<select
|
||
value={filterRegulation}
|
||
onChange={(e) => setFilterRegulation(e.target.value)}
|
||
className="px-3 py-1.5 text-sm border border-slate-300 rounded-lg"
|
||
>
|
||
<option value="all">Alle Regulierungen</option>
|
||
{uniqueRegulations.map(r => (
|
||
<option key={r} value={r}>{r.toUpperCase()}</option>
|
||
))}
|
||
</select>
|
||
<select
|
||
value={filterPriority}
|
||
onChange={(e) => setFilterPriority(e.target.value)}
|
||
className="px-3 py-1.5 text-sm border border-slate-300 rounded-lg"
|
||
>
|
||
<option value="all">Alle Prioritaeten</option>
|
||
<option value="critical">Kritisch</option>
|
||
<option value="high">Hoch</option>
|
||
<option value="medium">Mittel</option>
|
||
<option value="low">Niedrig</option>
|
||
</select>
|
||
<select
|
||
value={filterResponsible}
|
||
onChange={(e) => setFilterResponsible(e.target.value)}
|
||
className="px-3 py-1.5 text-sm border border-slate-300 rounded-lg"
|
||
>
|
||
<option value="all">Alle Verantwortlichen</option>
|
||
{uniqueResponsibles.map(r => (
|
||
<option key={r} value={r}>{r}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Obligations Table */}
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead className="bg-slate-50">
|
||
<tr>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">ID</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Pflicht</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Kategorie</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Verantwortlich</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Frist</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Prioritaet</th>
|
||
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Sanktion</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-slate-200">
|
||
{filteredObligations.map(obl => {
|
||
const priorityColors = PRIORITY_COLORS[obl.priority]
|
||
return (
|
||
<tr key={obl.id} className="hover:bg-slate-50">
|
||
<td className="px-4 py-3">
|
||
<span className="font-mono text-sm text-purple-600">{obl.id}</span>
|
||
</td>
|
||
<td className="px-4 py-3">
|
||
<p className="font-medium text-slate-900">{obl.title}</p>
|
||
<p className="text-xs text-slate-500 mt-1 line-clamp-2">{obl.description}</p>
|
||
</td>
|
||
<td className="px-4 py-3">
|
||
<span className="flex items-center gap-1">
|
||
<span>{CATEGORY_ICONS[obl.category.toLowerCase()] || '📋'}</span>
|
||
<span className="text-sm text-slate-600 capitalize">{obl.category}</span>
|
||
</span>
|
||
</td>
|
||
<td className="px-4 py-3 text-sm text-slate-600">{obl.responsible}</td>
|
||
<td className="px-4 py-3">
|
||
{obl.deadline ? (
|
||
<span className="text-sm text-slate-600">
|
||
{obl.deadline.date
|
||
? new Date(obl.deadline.date).toLocaleDateString('de-DE')
|
||
: obl.deadline.duration}
|
||
</span>
|
||
) : (
|
||
<span className="text-sm text-slate-400">-</span>
|
||
)}
|
||
</td>
|
||
<td className="px-4 py-3">
|
||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${priorityColors.bg} ${priorityColors.text}`}>
|
||
{obl.priority === 'critical' ? 'Kritisch' :
|
||
obl.priority === 'high' ? 'Hoch' :
|
||
obl.priority === 'medium' ? 'Mittel' : 'Niedrig'}
|
||
</span>
|
||
</td>
|
||
<td className="px-4 py-3">
|
||
{obl.sanctions ? (
|
||
<div>
|
||
<p className="text-sm font-medium text-red-600">{obl.sanctions.max_fine}</p>
|
||
{obl.sanctions.personal_liability && (
|
||
<p className="text-xs text-red-500">+ pers. Haftung</p>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<span className="text-sm text-slate-400">-</span>
|
||
)}
|
||
</td>
|
||
</tr>
|
||
)
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Key Risks and Recommendations */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{/* Key Risks */}
|
||
<div className="bg-red-50 border border-red-200 rounded-xl p-6">
|
||
<h3 className="font-semibold text-red-800 flex items-center gap-2 mb-4">
|
||
<span>⚠️</span> Hauptrisiken
|
||
</h3>
|
||
<ul className="space-y-2">
|
||
{overview.executive_summary.key_risks.map((risk, i) => (
|
||
<li key={i} className="flex items-start gap-2 text-sm text-red-700">
|
||
<span className="text-red-500">•</span>
|
||
{risk}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
{/* Recommendations */}
|
||
<div className="bg-green-50 border border-green-200 rounded-xl p-6">
|
||
<h3 className="font-semibold text-green-800 flex items-center gap-2 mb-4">
|
||
<span>✅</span> Empfohlene Massnahmen
|
||
</h3>
|
||
<ol className="space-y-2">
|
||
{overview.executive_summary.recommended_actions.map((action, i) => (
|
||
<li key={i} className="flex items-start gap-2 text-sm text-green-700">
|
||
<span className="font-medium text-green-600">{i + 1}.</span>
|
||
{action}
|
||
</li>
|
||
))}
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Mock data generator for demo purposes
|
||
function generateMockOverview(
|
||
orgName: string,
|
||
sector: string,
|
||
sizeCategory: string,
|
||
specialServices: string[],
|
||
isKritis: boolean,
|
||
usesAI: boolean = false,
|
||
aiHighRiskCategories: string[] = []
|
||
): ObligationsOverview {
|
||
const isEssential = sizeCategory === 'large' || isKritis || specialServices.length > 0
|
||
const isImportant = sizeCategory === 'medium' && !isEssential
|
||
const isNIS2Affected = isEssential || isImportant
|
||
const isAIHighRisk = usesAI && aiHighRiskCategories.length > 0
|
||
|
||
const regulations: ApplicableRegulation[] = [
|
||
{
|
||
id: 'dsgvo',
|
||
name: 'DSGVO',
|
||
classification: 'Anwendbar',
|
||
reason: 'Verarbeitung personenbezogener Daten',
|
||
obligation_count: 8,
|
||
},
|
||
]
|
||
|
||
if (isNIS2Affected) {
|
||
regulations.push({
|
||
id: 'nis2',
|
||
name: 'NIS2-Richtlinie / BSIG-E',
|
||
classification: isEssential ? 'Besonders wichtige Einrichtung' : 'Wichtige Einrichtung',
|
||
reason: isEssential
|
||
? 'Sektor in Anhang I und Groessenkriterien erfuellt'
|
||
: 'Sektor in Anhang II und mittleres Unternehmen',
|
||
obligation_count: isEssential ? 14 : 12,
|
||
})
|
||
}
|
||
|
||
if (usesAI) {
|
||
regulations.push({
|
||
id: 'ai_act',
|
||
name: 'EU AI Act',
|
||
classification: isAIHighRisk ? 'Hochrisiko-KI' : 'Begrenztes Risiko',
|
||
reason: isAIHighRisk
|
||
? 'KI-Einsatz in Hochrisiko-Kategorien (Annex III)'
|
||
: 'KI-Einsatz mit Transparenzpflichten',
|
||
obligation_count: isAIHighRisk ? 8 : 3,
|
||
})
|
||
}
|
||
|
||
const obligations: Obligation[] = []
|
||
|
||
// NIS2 Obligations
|
||
if (isNIS2Affected) {
|
||
obligations.push(
|
||
{
|
||
id: 'NIS2-OBL-001',
|
||
regulation_id: 'nis2',
|
||
title: 'BSI-Registrierung',
|
||
description: 'Registrierung beim BSI ueber das Meldeportal mit Kontaktdaten, IP-Bereichen und Zustaendigkeiten.',
|
||
legal_basis: [{ norm: '§ 33 BSIG-E' }],
|
||
category: 'Meldepflicht',
|
||
responsible: 'Geschaeftsfuehrung',
|
||
deadline: { type: 'absolute', date: '2025-01-17' },
|
||
sanctions: { max_fine: '500.000 EUR' },
|
||
evidence: ['Registrierungsbestaetigung BSI'],
|
||
priority: 'critical',
|
||
},
|
||
{
|
||
id: 'NIS2-OBL-002',
|
||
regulation_id: 'nis2',
|
||
title: 'Risikomanagement-Massnahmen',
|
||
description: 'Implementierung angemessener technischer und organisatorischer Massnahmen zur Beherrschung von Risiken.',
|
||
legal_basis: [{ norm: 'Art. 21 NIS2' }, { norm: '§ 30 BSIG-E' }],
|
||
category: 'Technisch',
|
||
responsible: 'CISO',
|
||
deadline: { type: 'relative', duration: '18 Monate nach Inkrafttreten' },
|
||
sanctions: { max_fine: '10 Mio. EUR oder 2% Jahresumsatz', personal_liability: true },
|
||
evidence: ['ISMS-Dokumentation', 'Risikoanalyse'],
|
||
priority: 'high',
|
||
},
|
||
{
|
||
id: 'NIS2-OBL-003',
|
||
regulation_id: 'nis2',
|
||
title: 'Schulung Leitungsorgane',
|
||
description: 'Regelmaessige Schulung der Geschaeftsfuehrung zu Cyberrisiken und Risikomanagement.',
|
||
legal_basis: [{ norm: 'Art. 20 Abs. 2 NIS2' }],
|
||
category: 'Personal',
|
||
responsible: 'Geschaeftsfuehrung',
|
||
deadline: { type: 'relative', duration: 'Jaehrlich' },
|
||
sanctions: { max_fine: '10 Mio. EUR', personal_liability: true },
|
||
evidence: ['Schulungsnachweise'],
|
||
priority: 'high',
|
||
},
|
||
{
|
||
id: 'NIS2-OBL-004',
|
||
regulation_id: 'nis2',
|
||
title: 'Incident-Meldeprozess',
|
||
description: 'Etablierung eines 24h/72h/1M Meldeprozesses fuer Sicherheitsvorfaelle an das BSI.',
|
||
legal_basis: [{ norm: '§ 32 BSIG-E' }],
|
||
category: 'Meldepflicht',
|
||
responsible: 'CISO',
|
||
sanctions: { max_fine: '500.000 EUR' },
|
||
evidence: ['Meldeprozess-Dokumentation', 'Erreichbarkeit 24/7'],
|
||
priority: 'critical',
|
||
},
|
||
{
|
||
id: 'NIS2-OBL-005',
|
||
regulation_id: 'nis2',
|
||
title: 'Supply-Chain-Security',
|
||
description: 'Sicherheitsanforderungen an Lieferanten und Dienstleister vertraglich festlegen.',
|
||
legal_basis: [{ norm: 'Art. 21 Abs. 2 lit. d NIS2' }],
|
||
category: 'Lieferkette',
|
||
responsible: 'Einkauf / CISO',
|
||
evidence: ['Lieferantenvertraege', 'Sicherheitsanforderungen'],
|
||
priority: 'medium',
|
||
}
|
||
)
|
||
|
||
if (isEssential) {
|
||
obligations.push({
|
||
id: 'NIS2-OBL-006',
|
||
regulation_id: 'nis2',
|
||
title: 'Regelmaessige Sicherheitspruefungen',
|
||
description: 'Durchfuehrung regelmaessiger Audits und Penetrationstests (nur besonders wichtige Einrichtungen).',
|
||
legal_basis: [{ norm: '§ 39 BSIG-E' }],
|
||
category: 'Audit',
|
||
responsible: 'CISO',
|
||
deadline: { type: 'relative', duration: 'Alle 2 Jahre' },
|
||
sanctions: { max_fine: '10 Mio. EUR' },
|
||
evidence: ['Audit-Berichte', 'Pentest-Reports'],
|
||
priority: 'high',
|
||
})
|
||
}
|
||
}
|
||
|
||
// DSGVO Obligations
|
||
obligations.push(
|
||
{
|
||
id: 'DSGVO-OBL-001',
|
||
regulation_id: 'dsgvo',
|
||
title: 'Verarbeitungsverzeichnis fuehren',
|
||
description: 'Dokumentation aller Verarbeitungstaetigkeiten gemaess Art. 30 DSGVO.',
|
||
legal_basis: [{ norm: 'Art. 30 DSGVO' }],
|
||
category: 'Governance',
|
||
responsible: 'DSB',
|
||
sanctions: { max_fine: '10 Mio. EUR oder 2% Jahresumsatz' },
|
||
evidence: ['VVT-Dokumentation'],
|
||
priority: 'high',
|
||
},
|
||
{
|
||
id: 'DSGVO-OBL-002',
|
||
regulation_id: 'dsgvo',
|
||
title: 'Technische und organisatorische Massnahmen',
|
||
description: 'Implementierung angemessener TOMs zum Schutz personenbezogener Daten.',
|
||
legal_basis: [{ norm: 'Art. 32 DSGVO' }],
|
||
category: 'Technisch',
|
||
responsible: 'IT-Leitung',
|
||
sanctions: { max_fine: '10 Mio. EUR' },
|
||
evidence: ['TOM-Dokumentation'],
|
||
priority: 'high',
|
||
},
|
||
{
|
||
id: 'DSGVO-OBL-003',
|
||
regulation_id: 'dsgvo',
|
||
title: 'Datenschutz-Folgenabschaetzung',
|
||
description: 'DSFA bei Verarbeitungen mit hohem Risiko fuer Betroffene.',
|
||
legal_basis: [{ norm: 'Art. 35 DSGVO' }],
|
||
category: 'Governance',
|
||
responsible: 'DSB',
|
||
sanctions: { max_fine: '10 Mio. EUR' },
|
||
evidence: ['DSFA-Dokumentation'],
|
||
priority: 'medium',
|
||
}
|
||
)
|
||
|
||
// AI Act Obligations
|
||
if (usesAI) {
|
||
// Transparency obligations for all AI systems
|
||
obligations.push({
|
||
id: 'AIACT-OBL-001',
|
||
regulation_id: 'ai_act',
|
||
title: 'Transparenzpflichten',
|
||
description: 'Nutzer muessen informiert werden, dass sie mit einem KI-System interagieren.',
|
||
legal_basis: [{ norm: 'Art. 50 AI Act' }],
|
||
category: 'Compliance',
|
||
responsible: 'KI-Verantwortlicher',
|
||
deadline: { type: 'absolute', date: '2025-08-02' },
|
||
sanctions: { max_fine: '15 Mio. EUR oder 3% Jahresumsatz' },
|
||
evidence: ['Transparenzhinweise', 'Nutzerschnittstellen-Screenshots'],
|
||
priority: 'high',
|
||
})
|
||
|
||
if (isAIHighRisk) {
|
||
// High-risk AI obligations
|
||
obligations.push(
|
||
{
|
||
id: 'AIACT-OBL-002',
|
||
regulation_id: 'ai_act',
|
||
title: 'Risikomanagement-System',
|
||
description: 'Einrichtung, Dokumentation und Pflege eines Risikomanagement-Systems fuer das KI-System.',
|
||
legal_basis: [{ norm: 'Art. 9 AI Act' }],
|
||
category: 'Governance',
|
||
responsible: 'KI-Verantwortlicher',
|
||
deadline: { type: 'absolute', date: '2026-08-02' },
|
||
sanctions: { max_fine: '35 Mio. EUR oder 7% Jahresumsatz' },
|
||
evidence: ['Risikomanagement-Dokumentation', 'Risikoregister'],
|
||
priority: 'critical',
|
||
},
|
||
{
|
||
id: 'AIACT-OBL-003',
|
||
regulation_id: 'ai_act',
|
||
title: 'Datengovernance',
|
||
description: 'Sicherstellung hoher Datenqualitaet fuer Training, Validierung und Tests.',
|
||
legal_basis: [{ norm: 'Art. 10 AI Act' }],
|
||
category: 'Technisch',
|
||
responsible: 'Data Engineer / KI-Team',
|
||
sanctions: { max_fine: '35 Mio. EUR oder 7% Jahresumsatz' },
|
||
evidence: ['Datenqualitaetsberichte', 'Bias-Analysen'],
|
||
priority: 'high',
|
||
},
|
||
{
|
||
id: 'AIACT-OBL-004',
|
||
regulation_id: 'ai_act',
|
||
title: 'Technische Dokumentation',
|
||
description: 'Erstellung umfassender technischer Dokumentation vor Inbetriebnahme.',
|
||
legal_basis: [{ norm: 'Art. 11 AI Act' }],
|
||
category: 'Governance',
|
||
responsible: 'KI-Verantwortlicher',
|
||
sanctions: { max_fine: '35 Mio. EUR oder 7% Jahresumsatz' },
|
||
evidence: ['Technische Dokumentation', 'System-Architektur'],
|
||
priority: 'high',
|
||
},
|
||
{
|
||
id: 'AIACT-OBL-005',
|
||
regulation_id: 'ai_act',
|
||
title: 'Menschliche Aufsicht',
|
||
description: 'Massnahmen fuer wirksame menschliche Aufsicht waehrend des Betriebs.',
|
||
legal_basis: [{ norm: 'Art. 14 AI Act' }],
|
||
category: 'Governance',
|
||
responsible: 'KI-Verantwortlicher',
|
||
sanctions: { max_fine: '35 Mio. EUR oder 7% Jahresumsatz' },
|
||
evidence: ['Aufsichtskonzept', 'Eingriffsprozesse'],
|
||
priority: 'critical',
|
||
},
|
||
{
|
||
id: 'AIACT-OBL-006',
|
||
regulation_id: 'ai_act',
|
||
title: 'Grundrechte-Folgenabschaetzung (FRIA)',
|
||
description: 'Durchfuehrung einer Folgenabschaetzung fuer Grundrechte vor Einsatz.',
|
||
legal_basis: [{ norm: 'Art. 27 AI Act' }],
|
||
category: 'Compliance',
|
||
responsible: 'KI-Verantwortlicher',
|
||
deadline: { type: 'relative', duration: 'Vor Inbetriebnahme' },
|
||
sanctions: { max_fine: '15 Mio. EUR oder 3% Jahresumsatz' },
|
||
evidence: ['FRIA-Dokumentation'],
|
||
priority: 'critical',
|
||
}
|
||
)
|
||
}
|
||
}
|
||
|
||
const incidentDeadlines: IncidentDeadline[] = isNIS2Affected ? [
|
||
{
|
||
regulation_id: 'nis2',
|
||
phase: 'Fruehwarnung',
|
||
deadline: '24 Stunden',
|
||
content: 'Erste Meldung ueber erheblichen Sicherheitsvorfall',
|
||
recipient: 'BSI',
|
||
legal_basis: '§ 32 Abs. 1 BSIG-E',
|
||
},
|
||
{
|
||
regulation_id: 'nis2',
|
||
phase: 'Vorfallmeldung',
|
||
deadline: '72 Stunden',
|
||
content: 'Schweregrad, IoCs, erste Bewertung',
|
||
recipient: 'BSI',
|
||
legal_basis: '§ 32 Abs. 2 BSIG-E',
|
||
},
|
||
{
|
||
regulation_id: 'nis2',
|
||
phase: 'Abschlussbericht',
|
||
deadline: '1 Monat',
|
||
content: 'Root Cause, ergriffene Massnahmen, Auswirkungen',
|
||
recipient: 'BSI',
|
||
legal_basis: '§ 32 Abs. 3 BSIG-E',
|
||
},
|
||
{
|
||
regulation_id: 'dsgvo',
|
||
phase: 'Meldung Datenschutzverletzung',
|
||
deadline: '72 Stunden',
|
||
content: 'Meldung bei Verletzung des Schutzes personenbezogener Daten',
|
||
recipient: 'Aufsichtsbehoerde',
|
||
legal_basis: 'Art. 33 DSGVO',
|
||
},
|
||
] : [
|
||
{
|
||
regulation_id: 'dsgvo',
|
||
phase: 'Meldung Datenschutzverletzung',
|
||
deadline: '72 Stunden',
|
||
content: 'Meldung bei Verletzung des Schutzes personenbezogener Daten',
|
||
recipient: 'Aufsichtsbehoerde',
|
||
legal_basis: 'Art. 33 DSGVO',
|
||
},
|
||
]
|
||
|
||
return {
|
||
id: `assessment-${Date.now()}`,
|
||
organization_name: orgName || 'Unbenannte Organisation',
|
||
assessment_date: new Date().toISOString(),
|
||
applicable_regulations: regulations,
|
||
obligations: obligations,
|
||
incident_deadlines: incidentDeadlines,
|
||
executive_summary: {
|
||
total_regulations: regulations.length,
|
||
total_obligations: obligations.length,
|
||
critical_obligations: obligations.filter(o => o.priority === 'critical').length,
|
||
overdue_obligations: 0,
|
||
upcoming_deadlines: obligations.filter(o => o.deadline?.date && new Date(o.deadline.date) < new Date('2025-03-01')).length,
|
||
key_risks: [
|
||
...(isNIS2Affected ? [
|
||
'BSI-Registrierung bis 17.01.2025 erforderlich',
|
||
'Persoenliche Haftung der Geschaeftsfuehrung bei Verstoessen',
|
||
'Meldepflichten mit kurzen Fristen (24h/72h)',
|
||
] : []),
|
||
...(isAIHighRisk ? [
|
||
'Hochrisiko-KI erfordert umfassende Dokumentation und Risikomanagement',
|
||
'Grundrechte-Folgenabschaetzung (FRIA) vor Inbetriebnahme erforderlich',
|
||
'Bussgelder bis 35 Mio. EUR oder 7% Jahresumsatz bei Verstoessen',
|
||
] : []),
|
||
...(usesAI && !isAIHighRisk ? [
|
||
'KI-Transparenzpflichten ab August 2025',
|
||
] : []),
|
||
'DSGVO-Dokumentationspflichten muessen erfuellt werden',
|
||
'Bei Datenschutzverletzungen: 72h Meldefrist',
|
||
],
|
||
recommended_actions: [
|
||
...(isNIS2Affected ? [
|
||
'Sofortige BSI-Registrierung einleiten',
|
||
'ISMS nach ISO 27001 oder BSI IT-Grundschutz aufbauen',
|
||
'Incident-Response-Plan mit 24h-Erreichbarkeit etablieren',
|
||
] : []),
|
||
...(isAIHighRisk ? [
|
||
'KI-Risikomanagement-System aufbauen',
|
||
'Technische Dokumentation fuer KI-Systeme erstellen',
|
||
'FRIA durchfuehren und dokumentieren',
|
||
'Menschliche Aufsicht implementieren',
|
||
] : []),
|
||
...(usesAI && !isAIHighRisk ? [
|
||
'Transparenzhinweise fuer KI-Interaktionen implementieren',
|
||
] : []),
|
||
'Verarbeitungsverzeichnis erstellen/aktualisieren',
|
||
'TOMs dokumentieren und regelmaessig pruefen',
|
||
],
|
||
},
|
||
sanctions_summary: {
|
||
max_financial_risk: isAIHighRisk
|
||
? '35 Mio. EUR oder 7% Jahresumsatz'
|
||
: (isNIS2Affected ? '10 Mio. EUR oder 2% Jahresumsatz' : '10 Mio. EUR'),
|
||
personal_liability_risk: isNIS2Affected,
|
||
criminal_risk: false,
|
||
summary: [
|
||
isNIS2Affected ? 'Hohe Sanktionsrisiken durch NIS2.' : '',
|
||
isAIHighRisk ? 'Sehr hohe Bussgelder durch AI Act bei Hochrisiko-KI.' : '',
|
||
usesAI && !isAIHighRisk ? 'Moderate Bussgelder bei AI Act Transparenzverletzungen.' : '',
|
||
'DSGVO-Bussgelder bis 10 Mio. EUR oder 2% des Jahresumsatzes.',
|
||
isNIS2Affected ? 'Persoenliche Haftung der Geschaeftsfuehrung moeglich.' : '',
|
||
].filter(s => s).join(' '),
|
||
},
|
||
}
|
||
}
|