Files
breakpilot-lehrer/website/app/admin/compliance/page.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

1520 lines
70 KiB
TypeScript

'use client'
/**
* Compliance & Audit Framework Dashboard
*
* Tabs:
* - Uebersicht: Dashboard mit Score, Stats, Quick Actions
* - Architektur: Systemarchitektur und Datenfluss
* - Roadmap: Implementierungsfortschritt und Backlog
* - Technisch: API-Dokumentation und Datenmodell
* - Audit: Export und Audit-Historie
* - Dokumentation: Handbuecher und Guides
*/
import { useState, useEffect } from 'react'
import Link from 'next/link'
import AdminLayout from '@/components/admin/AdminLayout'
import LanguageSwitch from '@/components/compliance/LanguageSwitch'
import LLMProviderToggle from '@/components/compliance/LLMProviderToggle'
import { Language } from '@/lib/compliance-i18n'
// Types
interface DashboardData {
compliance_score: number
total_regulations: number
total_requirements: number
total_controls: number
controls_by_status: Record<string, number>
controls_by_domain: Record<string, Record<string, number>>
total_evidence: number
evidence_by_status: Record<string, number>
total_risks: number
risks_by_level: Record<string, number>
}
interface AIStatus {
provider: string
model: string
is_available: boolean
is_mock: boolean
}
interface Regulation {
id: string
code: string
name: string
full_name: string
regulation_type: string
effective_date: string | null
description: string
requirement_count: number
}
// Tab definitions
type TabId = 'executive' | 'uebersicht' | 'architektur' | 'roadmap' | 'technisch' | 'audit' | 'dokumentation'
const tabs: { id: TabId; name: string; icon: JSX.Element }[] = [
{
id: 'executive',
name: 'Executive',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
),
},
{
id: 'uebersicht',
name: 'Uebersicht',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
),
},
{
id: 'architektur',
name: 'Architektur',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
),
},
{
id: 'roadmap',
name: 'Roadmap',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
),
},
{
id: 'technisch',
name: 'Technisch',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
),
},
{
id: 'audit',
name: 'Audit',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
),
},
{
id: 'dokumentation',
name: 'Dokumentation',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
),
},
]
const DOMAIN_LABELS: Record<string, string> = {
gov: 'Governance',
priv: 'Datenschutz',
iam: 'Identity & Access',
crypto: 'Kryptografie',
sdlc: 'Secure Dev',
ops: 'Operations',
ai: 'KI-spezifisch',
cra: 'Supply Chain',
aud: 'Audit',
}
const STATUS_COLORS: Record<string, string> = {
pass: 'bg-green-500',
partial: 'bg-yellow-500',
fail: 'bg-red-500',
planned: 'bg-slate-400',
'n/a': 'bg-slate-300',
}
// Backlog items for Roadmap - Updated 2026-01-17
const BACKLOG_ITEMS = [
{ id: 1, title: 'EU-Lex Live-Fetch (19 Regulations)', priority: 'high', status: 'completed', category: 'Integration' },
{ id: 2, title: 'BSI-TR-03161 PDF Parser (3 PDFs)', priority: 'high', status: 'completed', category: 'Data Import' },
{ id: 3, title: 'AI-Interpretation fuer Requirements', priority: 'high', status: 'completed', category: 'AI' },
{ id: 4, title: 'Auto-Mapping Controls zu Requirements (474)', priority: 'high', status: 'completed', category: 'Automation' },
{ id: 5, title: 'Service-Modul-Registry (30 Module)', priority: 'high', status: 'completed', category: 'Architecture' },
{ id: 6, title: 'Audit Trail fuer alle Aenderungen', priority: 'high', status: 'completed', category: 'Audit' },
{ id: 7, title: 'Automatische Evidence-Sammlung aus CI/CD', priority: 'high', status: 'planned', category: 'Automation' },
{ id: 8, title: 'Control-Review Workflow mit Benachrichtigungen', priority: 'medium', status: 'planned', category: 'Workflow' },
{ id: 9, title: 'Risk Treatment Plan Tracking', priority: 'medium', status: 'planned', category: 'Risk Management' },
{ id: 10, title: 'Compliance Score Trend-Analyse', priority: 'low', status: 'planned', category: 'Analytics' },
{ id: 11, title: 'SBOM Integration fuer CRA Compliance', priority: 'medium', status: 'planned', category: 'Integration' },
{ id: 12, title: 'Multi-Mandanten Compliance Trennung', priority: 'medium', status: 'planned', category: 'Architecture' },
{ id: 13, title: 'Compliance Report PDF Generator', priority: 'medium', status: 'planned', category: 'Export' },
]
// Executive Dashboard Types
interface ExecutiveDashboardData {
traffic_light_status: 'green' | 'yellow' | 'red'
overall_score: number
score_trend: { date: string; score: number; label: string }[]
previous_score: number | null
score_change: number | null
total_regulations: number
total_requirements: number
total_controls: number
open_risks: number
top_risks: {
id: string
risk_id: string
title: string
risk_level: string
owner: string | null
status: string
category: string
impact: number
likelihood: number
}[]
upcoming_deadlines: {
id: string
title: string
deadline: string
days_remaining: number
type: string
status: string
owner: string | null
}[]
team_workload: {
name: string
pending_tasks: number
in_progress_tasks: number
completed_tasks: number
total_tasks: number
completion_rate: number
}[]
last_updated: string
}
// Main Component
export default function CompliancePage() {
const [activeTab, setActiveTab] = useState<TabId>('executive')
const [dashboard, setDashboard] = useState<DashboardData | null>(null)
const [regulations, setRegulations] = useState<Regulation[]>([])
const [aiStatus, setAiStatus] = useState<AIStatus | null>(null)
const [loading, setLoading] = useState(true)
const [seeding, setSeeding] = useState(false)
const [error, setError] = useState<string | null>(null)
const [language, setLanguage] = useState<Language>('de')
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000'
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
setLoading(true)
setError(null)
try {
const [dashboardRes, regulationsRes, aiStatusRes] = await Promise.all([
fetch(`${BACKEND_URL}/api/v1/compliance/dashboard`),
fetch(`${BACKEND_URL}/api/v1/compliance/regulations`),
fetch(`${BACKEND_URL}/api/v1/compliance/ai/status`),
])
if (dashboardRes.ok) {
setDashboard(await dashboardRes.json())
}
if (regulationsRes.ok) {
const data = await regulationsRes.json()
setRegulations(data.regulations || [])
}
if (aiStatusRes.ok) {
setAiStatus(await aiStatusRes.json())
}
} catch (err) {
console.error('Failed to load compliance data:', err)
setError('Verbindung zum Backend fehlgeschlagen')
} finally {
setLoading(false)
}
}
const seedDatabase = async () => {
setSeeding(true)
try {
const res = await fetch(`${BACKEND_URL}/api/v1/compliance/seed`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ force: false }),
})
if (res.ok) {
const result = await res.json()
alert(`Datenbank erfolgreich initialisiert!\n\nRegulations: ${result.counts.regulations}\nControls: ${result.counts.controls}\nRequirements: ${result.counts.requirements}\nMappings: ${result.counts.mappings}`)
loadData()
} else {
const error = await res.text()
alert(`Fehler beim Seeding: ${error}`)
}
} catch (err) {
console.error('Seeding failed:', err)
alert('Fehler beim Initialisieren der Datenbank')
} finally {
setSeeding(false)
}
}
return (
<AdminLayout title="Compliance & Audit" description="Regulatory Compliance Framework">
{/* Error Banner */}
{error && (
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4 flex items-center gap-3">
<svg className="w-5 h-5 text-red-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-red-700">{error}</span>
<button onClick={loadData} className="ml-auto text-red-600 hover:text-red-800 text-sm font-medium">
Erneut versuchen
</button>
</div>
)}
{/* Action Bar */}
<div className="mb-6 flex items-center justify-between">
<div className="flex items-center gap-3">
<Link
href="/admin/compliance/scraper"
className="inline-flex items-center gap-2 px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Scraper oeffnen
</Link>
<Link
href="/admin/compliance/audit-workspace"
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
Audit Workspace
</Link>
</div>
<div className="flex items-center gap-3">
<LLMProviderToggle aiStatus={aiStatus} onStatusChange={loadData} />
<LanguageSwitch onChange={setLanguage} />
<button
onClick={loadData}
className="inline-flex items-center gap-2 px-3 py-2 text-sm text-slate-600 hover:text-slate-900 hover:bg-slate-100 rounded-lg transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
{language === 'de' ? 'Aktualisieren' : 'Refresh'}
</button>
</div>
</div>
{/* Seed Button if no data */}
{!loading && (dashboard?.total_controls || 0) === 0 && (
<div className="mb-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<div className="flex items-center justify-between">
<div>
<p className="font-medium text-yellow-800">Keine Compliance-Daten vorhanden</p>
<p className="text-sm text-yellow-700">Initialisieren Sie die Datenbank mit den Seed-Daten.</p>
</div>
<button
onClick={seedDatabase}
disabled={seeding}
className="px-4 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 disabled:opacity-50"
>
{seeding ? 'Initialisiere...' : 'Datenbank initialisieren'}
</button>
</div>
</div>
)}
{/* Tab Navigation */}
<div className="border-b border-slate-200 mb-6">
<nav className="-mb-px flex space-x-8 overflow-x-auto">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`
flex items-center gap-2 py-4 px-1 border-b-2 font-medium text-sm transition-colors whitespace-nowrap
${activeTab === tab.id
? 'border-primary-500 text-primary-600'
: 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'
}
`}
>
{tab.icon}
{tab.name}
</button>
))}
</nav>
</div>
{/* Tab Content */}
{activeTab === 'executive' && (
<ExecutiveTab loading={loading} onRefresh={loadData} />
)}
{activeTab === 'uebersicht' && (
<UebersichtTab
dashboard={dashboard}
regulations={regulations}
aiStatus={aiStatus}
loading={loading}
onRefresh={loadData}
/>
)}
{activeTab === 'architektur' && <ArchitekturTab />}
{activeTab === 'roadmap' && <RoadmapTab />}
{activeTab === 'technisch' && <TechnischTab />}
{activeTab === 'audit' && <AuditTab />}
{activeTab === 'dokumentation' && <DokumentationTab />}
</AdminLayout>
)
}
// ============================================================================
// Uebersicht Tab
// ============================================================================
function UebersichtTab({
dashboard,
regulations,
aiStatus,
loading,
onRefresh,
}: {
dashboard: DashboardData | null
regulations: Regulation[]
aiStatus: AIStatus | null
loading: boolean
onRefresh: () => void
}) {
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600" />
</div>
)
}
const score = dashboard?.compliance_score || 0
const scoreColor = score >= 80 ? 'text-green-600' : score >= 60 ? 'text-yellow-600' : 'text-red-600'
return (
<div className="space-y-6">
{/* AI Status Banner */}
{aiStatus && (
<div className={`rounded-lg p-4 flex items-center justify-between ${
aiStatus.is_available && !aiStatus.is_mock
? 'bg-gradient-to-r from-purple-50 to-pink-50 border border-purple-200'
: aiStatus.is_mock
? 'bg-yellow-50 border border-yellow-200'
: 'bg-red-50 border border-red-200'
}`}>
<div className="flex items-center gap-3">
<span className="text-2xl">🤖</span>
<div>
<div className="font-medium text-slate-900">
AI-Compliance-Assistent {aiStatus.is_available ? 'aktiv' : 'nicht verfuegbar'}
</div>
<div className="text-sm text-slate-600">
{aiStatus.is_mock ? (
<span className="text-yellow-700">Mock-Modus (kein API-Key konfiguriert)</span>
) : (
<>Provider: <span className="font-mono">{aiStatus.provider}</span> | Modell: <span className="font-mono">{aiStatus.model}</span></>
)}
</div>
</div>
</div>
<div className={`px-3 py-1 rounded-full text-sm font-medium ${
aiStatus.is_available && !aiStatus.is_mock
? 'bg-green-100 text-green-700'
: aiStatus.is_mock
? 'bg-yellow-100 text-yellow-700'
: 'bg-red-100 text-red-700'
}`}>
{aiStatus.is_available && !aiStatus.is_mock ? 'Online' : aiStatus.is_mock ? 'Mock' : 'Offline'}
</div>
</div>
)}
{/* Score and Stats Row */}
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
{/* Score Card */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-sm font-medium text-slate-500 mb-4">Compliance Score</h3>
<div className={`text-5xl font-bold ${scoreColor}`}>
{score.toFixed(0)}%
</div>
<div className="mt-4 h-2 bg-slate-200 rounded-full overflow-hidden">
<div
className={`h-full transition-all duration-500 ${score >= 80 ? 'bg-green-500' : score >= 60 ? 'bg-yellow-500' : 'bg-red-500'}`}
style={{ width: `${score}%` }}
/>
</div>
<p className="mt-2 text-sm text-slate-500">
{dashboard?.controls_by_status?.pass || 0} von {dashboard?.total_controls || 0} Controls bestanden
</p>
</div>
{/* Stats Cards */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-slate-500">Verordnungen</p>
<p className="text-2xl font-bold text-slate-900">{dashboard?.total_regulations || 0}</p>
</div>
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 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>
</div>
</div>
<p className="mt-2 text-sm text-slate-500">{dashboard?.total_requirements || 0} Anforderungen</p>
</div>
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-slate-500">Controls</p>
<p className="text-2xl font-bold text-slate-900">{dashboard?.total_controls || 0}</p>
</div>
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
<svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<p className="mt-2 text-sm text-slate-500">{dashboard?.controls_by_status?.pass || 0} bestanden</p>
</div>
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-slate-500">Nachweise</p>
<p className="text-2xl font-bold text-slate-900">{dashboard?.total_evidence || 0}</p>
</div>
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<svg className="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
</div>
</div>
<p className="mt-2 text-sm text-slate-500">{dashboard?.evidence_by_status?.valid || 0} aktiv</p>
</div>
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-slate-500">Risiken</p>
<p className="text-2xl font-bold text-slate-900">{dashboard?.total_risks || 0}</p>
</div>
<div className="w-10 h-10 bg-red-100 rounded-lg flex items-center justify-center">
<svg className="w-5 h-5 text-red-600" 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>
</div>
<p className="mt-2 text-sm text-slate-500">
{(dashboard?.risks_by_level?.high || 0) + (dashboard?.risks_by_level?.critical || 0)} kritisch
</p>
</div>
</div>
{/* Domain Chart and Quick Actions */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Domain Chart */}
<div className="lg:col-span-2 bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Controls nach Domain</h3>
<div className="space-y-4">
{Object.entries(dashboard?.controls_by_domain || {}).map(([domain, stats]) => {
const total = stats.total || 0
const pass = stats.pass || 0
const partial = stats.partial || 0
const passPercent = total > 0 ? ((pass + partial * 0.5) / total) * 100 : 0
return (
<div key={domain}>
<div className="flex justify-between text-sm mb-1">
<span className="font-medium text-slate-700">
{DOMAIN_LABELS[domain] || domain.toUpperCase()}
</span>
<span className="text-slate-500">
{pass}/{total} ({passPercent.toFixed(0)}%)
</span>
</div>
<div className="h-2 bg-slate-200 rounded-full overflow-hidden flex">
<div className="bg-green-500 h-full" style={{ width: `${(pass / total) * 100}%` }} />
<div className="bg-yellow-500 h-full" style={{ width: `${(partial / total) * 100}%` }} />
</div>
</div>
)
})}
</div>
</div>
{/* Quick Actions */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Schnellaktionen</h3>
<div className="grid grid-cols-2 gap-3">
<Link
href="/admin/compliance/controls"
className="p-4 rounded-lg border border-slate-200 hover:border-primary-500 hover:bg-primary-50 transition-colors"
>
<div className="text-primary-600 mb-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<p className="font-medium text-slate-900 text-sm">Controls</p>
</Link>
<Link
href="/admin/compliance/evidence"
className="p-4 rounded-lg border border-slate-200 hover:border-primary-500 hover:bg-primary-50 transition-colors"
>
<div className="text-purple-600 mb-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
</div>
<p className="font-medium text-slate-900 text-sm">Evidence</p>
</Link>
<Link
href="/admin/compliance/risks"
className="p-4 rounded-lg border border-slate-200 hover:border-primary-500 hover:bg-primary-50 transition-colors"
>
<div className="text-red-600 mb-2">
<svg className="w-6 h-6" 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>
<p className="font-medium text-slate-900 text-sm">Risiken</p>
</Link>
<Link
href="/admin/compliance/scraper"
className="p-4 rounded-lg border border-slate-200 hover:border-orange-500 hover:bg-orange-50 transition-colors"
>
<div className="text-orange-600 mb-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</div>
<p className="font-medium text-slate-900 text-sm">Scraper</p>
</Link>
<Link
href="/admin/compliance/export"
className="p-4 rounded-lg border border-slate-200 hover:border-primary-500 hover:bg-primary-50 transition-colors"
>
<div className="text-green-600 mb-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
</div>
<p className="font-medium text-slate-900 text-sm">Export</p>
</Link>
<Link
href="/admin/compliance/audit-workspace"
className="p-4 rounded-lg border border-slate-200 hover:border-blue-500 hover:bg-blue-50 transition-colors"
>
<div className="text-blue-600 mb-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
</div>
<p className="font-medium text-slate-900 text-sm">Audit Workspace</p>
</Link>
<Link
href="/admin/compliance/modules"
className="p-4 rounded-lg border border-slate-200 hover:border-pink-500 hover:bg-pink-50 transition-colors"
>
<div className="text-pink-600 mb-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
</div>
<p className="font-medium text-slate-900 text-sm">Service Module Registry</p>
</Link>
</div>
</div>
</div>
{/* Regulations Table */}
<div className="bg-white rounded-xl shadow-sm border overflow-hidden">
<div className="p-4 border-b flex items-center justify-between">
<h3 className="text-lg font-semibold text-slate-900">Verordnungen & Standards</h3>
<button onClick={onRefresh} className="text-sm text-primary-600 hover:text-primary-700">
Aktualisieren
</button>
</div>
<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">Code</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Name</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Typ</th>
<th className="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase">Anforderungen</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-200">
{regulations.slice(0, 10).map((reg) => (
<tr key={reg.id} className="hover:bg-slate-50">
<td className="px-4 py-3">
<span className="font-mono font-medium text-primary-600">{reg.code}</span>
</td>
<td className="px-4 py-3">
<p className="font-medium text-slate-900">{reg.name}</p>
</td>
<td className="px-4 py-3">
<span className={`px-2 py-1 text-xs rounded-full ${
reg.regulation_type === 'eu_regulation' ? 'bg-blue-100 text-blue-700' :
reg.regulation_type === 'eu_directive' ? 'bg-purple-100 text-purple-700' :
reg.regulation_type === 'bsi_standard' ? 'bg-green-100 text-green-700' :
'bg-slate-100 text-slate-700'
}`}>
{reg.regulation_type === 'eu_regulation' ? 'EU-VO' :
reg.regulation_type === 'eu_directive' ? 'EU-RL' :
reg.regulation_type === 'bsi_standard' ? 'BSI' :
reg.regulation_type === 'de_law' ? 'DE' : reg.regulation_type}
</span>
</td>
<td className="px-4 py-3 text-center">
<span className="font-medium">{reg.requirement_count}</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)
}
// ============================================================================
// Executive Tab (Phase 3 - Sprint 1)
// ============================================================================
import dynamic from 'next/dynamic'
// Dynamically import Recharts components to avoid SSR issues
const ComplianceTrendChart = dynamic(
() => import('@/components/compliance/charts/ComplianceTrendChart'),
{ ssr: false, loading: () => <div className="h-48 bg-slate-100 animate-pulse rounded" /> }
)
function ExecutiveTab({
loading,
onRefresh,
}: {
loading: boolean
onRefresh: () => void
}) {
const [executiveData, setExecutiveData] = useState<ExecutiveDashboardData | null>(null)
const [execLoading, setExecLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000'
useEffect(() => {
loadExecutiveData()
}, [])
const loadExecutiveData = async () => {
setExecLoading(true)
setError(null)
try {
const res = await fetch(`${BACKEND_URL}/api/v1/compliance/dashboard/executive`)
if (res.ok) {
setExecutiveData(await res.json())
} else {
setError('Executive Dashboard konnte nicht geladen werden')
}
} catch (err) {
console.error('Failed to load executive dashboard:', err)
setError('Verbindung zum Backend fehlgeschlagen')
} finally {
setExecLoading(false)
}
}
if (execLoading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600" />
</div>
)
}
if (error || !executiveData) {
return (
<div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
<p className="text-red-700">{error || 'Keine Daten verfuegbar'}</p>
<button
onClick={loadExecutiveData}
className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
Erneut versuchen
</button>
</div>
)
}
const { traffic_light_status, overall_score, score_trend, score_change, top_risks, upcoming_deadlines, team_workload } = executiveData
const trafficLightColors = {
green: { bg: 'bg-green-500', ring: 'ring-green-200', text: 'text-green-700', label: 'Gut' },
yellow: { bg: 'bg-yellow-500', ring: 'ring-yellow-200', text: 'text-yellow-700', label: 'Achtung' },
red: { bg: 'bg-red-500', ring: 'ring-red-200', text: 'text-red-700', label: 'Kritisch' },
}
const tlConfig = trafficLightColors[traffic_light_status]
return (
<div className="space-y-6">
{/* Header Row: Traffic Light + Key Metrics */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Traffic Light Status */}
<div className="bg-white rounded-xl shadow-sm border p-6 flex flex-col items-center justify-center">
<div
className={`w-28 h-28 rounded-full flex items-center justify-center ${tlConfig.bg} ring-8 ${tlConfig.ring} shadow-lg mb-4`}
>
<span className="text-4xl font-bold text-white">{overall_score.toFixed(0)}%</span>
</div>
<p className={`text-lg font-semibold ${tlConfig.text}`}>{tlConfig.label}</p>
<p className="text-sm text-slate-500 mt-1">Erfuellungsgrad</p>
{score_change !== null && (
<p className={`text-sm mt-2 ${score_change >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{score_change >= 0 ? '↑' : '↓'} {Math.abs(score_change).toFixed(1)}% zum Vormonat
</p>
)}
</div>
{/* Metric Cards */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between mb-2">
<p className="text-sm text-slate-500">Verordnungen</p>
<span className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
<svg className="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 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>
</span>
</div>
<p className="text-3xl font-bold text-slate-900">{executiveData.total_regulations}</p>
<p className="text-sm text-slate-500 mt-1">{executiveData.total_requirements} Anforderungen</p>
</div>
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between mb-2">
<p className="text-sm text-slate-500">Massnahmen</p>
<span className="w-8 h-8 bg-green-100 rounded-lg flex items-center justify-center">
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</span>
</div>
<p className="text-3xl font-bold text-slate-900">{executiveData.total_controls}</p>
<p className="text-sm text-slate-500 mt-1">Technische Controls</p>
</div>
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between mb-2">
<p className="text-sm text-slate-500">Offene Risiken</p>
<span className="w-8 h-8 bg-red-100 rounded-lg flex items-center justify-center">
<svg className="w-4 h-4 text-red-600" 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>
</span>
</div>
<p className="text-3xl font-bold text-slate-900">{executiveData.open_risks}</p>
<p className="text-sm text-slate-500 mt-1">Unmitigiert</p>
</div>
</div>
{/* Charts Row */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Trend Chart */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-slate-900">Compliance-Trend (12 Monate)</h3>
<button onClick={loadExecutiveData} className="text-sm text-primary-600 hover:text-primary-700">
Aktualisieren
</button>
</div>
<div className="h-48">
<ComplianceTrendChart data={score_trend} lang="de" height={180} />
</div>
</div>
{/* Top Risks */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Top 5 Risiken</h3>
{top_risks.length === 0 ? (
<p className="text-slate-500 text-center py-8">Keine offenen Risiken</p>
) : (
<div className="space-y-3">
{top_risks.map((risk) => {
const riskColors = {
critical: 'bg-red-100 text-red-700 border-red-200',
high: 'bg-orange-100 text-orange-700 border-orange-200',
medium: 'bg-yellow-100 text-yellow-700 border-yellow-200',
low: 'bg-green-100 text-green-700 border-green-200',
}
return (
<div key={risk.id} className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
<span className={`px-2 py-1 text-xs font-medium rounded border ${riskColors[risk.risk_level as keyof typeof riskColors] || riskColors.medium}`}>
{risk.risk_level.toUpperCase()}
</span>
<div className="flex-1 min-w-0">
<p className="font-medium text-slate-900 truncate">{risk.title}</p>
<p className="text-xs text-slate-500">{risk.owner || 'Kein Owner'}</p>
</div>
<span className="text-xs text-slate-400">{risk.risk_id}</span>
</div>
)
})}
</div>
)}
</div>
</div>
{/* Bottom Row: Deadlines + Workload */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Upcoming Deadlines */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Naechste Fristen</h3>
{upcoming_deadlines.length === 0 ? (
<p className="text-slate-500 text-center py-8">Keine anstehenden Fristen</p>
) : (
<div className="space-y-2">
{upcoming_deadlines.slice(0, 5).map((deadline) => {
const statusColors = {
overdue: 'bg-red-100 text-red-700',
at_risk: 'bg-yellow-100 text-yellow-700',
on_track: 'bg-green-100 text-green-700',
}
return (
<div key={deadline.id} className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
<span className={`px-2 py-1 text-xs font-medium rounded ${statusColors[deadline.status as keyof typeof statusColors] || statusColors.on_track}`}>
{deadline.days_remaining < 0
? `${Math.abs(deadline.days_remaining)}d ueberfaellig`
: deadline.days_remaining === 0
? 'Heute'
: `${deadline.days_remaining}d`}
</span>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-slate-900 truncate">{deadline.title}</p>
<p className="text-xs text-slate-500">{new Date(deadline.deadline).toLocaleDateString('de-DE')}</p>
</div>
</div>
)
})}
</div>
)}
</div>
{/* Team Workload */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Team-Auslastung</h3>
{team_workload.length === 0 ? (
<p className="text-slate-500 text-center py-8">Keine Daten verfuegbar</p>
) : (
<div className="space-y-3">
{team_workload.slice(0, 5).map((member) => (
<div key={member.name}>
<div className="flex justify-between text-sm mb-1">
<span className="font-medium text-slate-700">{member.name}</span>
<span className="text-slate-500">
{member.completed_tasks}/{member.total_tasks} ({member.completion_rate.toFixed(0)}%)
</span>
</div>
<div className="h-2 bg-slate-200 rounded-full overflow-hidden flex">
<div
className="bg-green-500 h-full"
style={{ width: `${(member.completed_tasks / member.total_tasks) * 100}%` }}
/>
<div
className="bg-yellow-500 h-full"
style={{ width: `${(member.in_progress_tasks / member.total_tasks) * 100}%` }}
/>
</div>
</div>
))}
</div>
)}
</div>
</div>
{/* Last Updated */}
<div className="text-right text-sm text-slate-400">
Zuletzt aktualisiert: {new Date(executiveData.last_updated).toLocaleString('de-DE')}
</div>
</div>
)
}
// ============================================================================
// Architektur Tab
// ============================================================================
function ArchitekturTab() {
return (
<div className="space-y-6">
<div className="bg-white rounded-xl shadow-sm border p-6">
<h2 className="text-xl font-semibold text-slate-900 mb-4">Systemarchitektur</h2>
<p className="text-slate-600 mb-6">
Das Compliance & Audit Framework ist modular aufgebaut und integriert sich nahtlos in die bestehende Breakpilot-Infrastruktur.
</p>
{/* Architecture Diagram */}
<div className="bg-slate-50 rounded-lg p-6 mb-6">
<pre className="text-sm text-slate-700 font-mono whitespace-pre overflow-x-auto">{`
┌─────────────────────────────────────────────────────────────────────────┐
│ COMPLIANCE FRAMEWORK │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Next.js │ │ FastAPI │ │ PostgreSQL │ │
│ │ Frontend │───▶│ Backend │───▶│ Database │ │
│ │ (Port 3000)│ │ (Port 8000)│ │ (Port 5432)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ │ │ │ │
│ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ │
│ │ Admin UI │ │ Compliance │ │ 7 Tables │ │
│ │ /admin/ │ │ Module │ │ compliance_│ │
│ │compliance/│ │ /backend/ │ │ regulations│ │
│ └───────────┘ │compliance/ │ │ _controls │ │
│ └───────────┘ │ _evidence │ │
│ │ _risks │ │
│ │ ... │ │
│ └───────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
`}</pre>
</div>
{/* Component Details */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="border rounded-lg p-4">
<h3 className="font-semibold text-slate-900 mb-2">Frontend (Next.js)</h3>
<ul className="text-sm text-slate-600 space-y-1">
<li>- Dashboard mit Compliance Score</li>
<li>- Control Catalogue mit Filtern</li>
<li>- Evidence Upload & Management</li>
<li>- Risk Matrix Visualisierung</li>
<li>- Audit Export Wizard</li>
</ul>
</div>
<div className="border rounded-lg p-4">
<h3 className="font-semibold text-slate-900 mb-2">Backend (FastAPI)</h3>
<ul className="text-sm text-slate-600 space-y-1">
<li>- REST API Endpoints</li>
<li>- Repository Pattern</li>
<li>- Pydantic Schemas</li>
<li>- Seeder Service</li>
<li>- Export Generator</li>
</ul>
</div>
<div className="border rounded-lg p-4">
<h3 className="font-semibold text-slate-900 mb-2">Datenbank (PostgreSQL)</h3>
<ul className="text-sm text-slate-600 space-y-1">
<li>- compliance_regulations</li>
<li>- compliance_requirements</li>
<li>- compliance_controls</li>
<li>- compliance_control_mappings</li>
<li>- compliance_evidence</li>
<li>- compliance_risks</li>
<li>- compliance_audit_exports</li>
</ul>
</div>
</div>
</div>
{/* Data Flow */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Datenfluss</h3>
<div className="space-y-4">
<div className="flex items-center gap-4 p-4 bg-blue-50 rounded-lg">
<div className="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold">1</div>
<div>
<p className="font-medium text-slate-900">Regulations & Requirements</p>
<p className="text-sm text-slate-600">EU-Verordnungen, BSI-Standards werden als Seed-Daten geladen</p>
</div>
</div>
<div className="flex items-center gap-4 p-4 bg-green-50 rounded-lg">
<div className="w-8 h-8 bg-green-500 text-white rounded-full flex items-center justify-center font-bold">2</div>
<div>
<p className="font-medium text-slate-900">Controls & Mappings</p>
<p className="text-sm text-slate-600">Technische Controls werden Requirements zugeordnet</p>
</div>
</div>
<div className="flex items-center gap-4 p-4 bg-purple-50 rounded-lg">
<div className="w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold">3</div>
<div>
<p className="font-medium text-slate-900">Evidence Collection</p>
<p className="text-sm text-slate-600">Nachweise werden manuell oder automatisiert erfasst</p>
</div>
</div>
<div className="flex items-center gap-4 p-4 bg-orange-50 rounded-lg">
<div className="w-8 h-8 bg-orange-500 text-white rounded-full flex items-center justify-center font-bold">4</div>
<div>
<p className="font-medium text-slate-900">Audit Export</p>
<p className="text-sm text-slate-600">ZIP-Pakete fuer externe Pruefer generieren</p>
</div>
</div>
</div>
</div>
</div>
)
}
// ============================================================================
// Roadmap Tab
// ============================================================================
function RoadmapTab() {
const completedCount = BACKLOG_ITEMS.filter(i => i.status === 'completed').length
const inProgressCount = BACKLOG_ITEMS.filter(i => i.status === 'in_progress').length
const plannedCount = BACKLOG_ITEMS.filter(i => i.status === 'planned').length
return (
<div className="space-y-6">
{/* Progress Overview */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-green-50 border border-green-200 rounded-xl p-6">
<p className="text-sm text-green-600 font-medium">Abgeschlossen</p>
<p className="text-3xl font-bold text-green-700">{completedCount}</p>
</div>
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-6">
<p className="text-sm text-yellow-600 font-medium">In Bearbeitung</p>
<p className="text-3xl font-bold text-yellow-700">{inProgressCount}</p>
</div>
<div className="bg-slate-50 border border-slate-200 rounded-xl p-6">
<p className="text-sm text-slate-600 font-medium">Geplant</p>
<p className="text-3xl font-bold text-slate-700">{plannedCount}</p>
</div>
</div>
{/* Implemented Features */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Implementierte Features (v2.0 - Stand: 2026-01-17)</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{[
'558 Requirements aus 19 Regulations extrahiert',
'44 Controls in 9 Domains mit Auto-Mapping',
'474 Control-Mappings automatisch generiert',
'30 Service-Module in Registry kartiert',
'AI-Interpretation fuer alle Requirements',
'EU-Lex Scraper fuer Live-Regulation-Fetch',
'BSI-TR-03161 PDF Parser (alle 3 Teile)',
'Evidence Management mit File Upload',
'Risk Matrix (5x5 Likelihood x Impact)',
'Audit Export Wizard (ZIP Generator)',
'Compliance Score Berechnung',
'Dashboard mit Echtzeit-Statistiken',
].map((feature, idx) => (
<div key={idx} className="flex items-center gap-2">
<svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-slate-700">{feature}</span>
</div>
))}
</div>
</div>
{/* Backlog */}
<div className="bg-white rounded-xl shadow-sm border overflow-hidden">
<div className="p-4 border-b">
<h3 className="text-lg font-semibold text-slate-900">Backlog</h3>
</div>
<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">Titel</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-center text-xs font-medium text-slate-500 uppercase">Prioritaet</th>
<th className="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-200">
{BACKLOG_ITEMS.map((item) => (
<tr key={item.id} className="hover:bg-slate-50">
<td className="px-4 py-3">
<span className="font-medium text-slate-900">{item.title}</span>
</td>
<td className="px-4 py-3">
<span className="text-sm text-slate-600">{item.category}</span>
</td>
<td className="px-4 py-3 text-center">
<span className={`px-2 py-1 text-xs rounded-full ${
item.priority === 'high' ? 'bg-red-100 text-red-700' :
item.priority === 'medium' ? 'bg-yellow-100 text-yellow-700' :
'bg-slate-100 text-slate-700'
}`}>
{item.priority}
</span>
</td>
<td className="px-4 py-3 text-center">
<span className={`px-2 py-1 text-xs rounded-full ${
item.status === 'completed' ? 'bg-green-100 text-green-700' :
item.status === 'in_progress' ? 'bg-blue-100 text-blue-700' :
'bg-slate-100 text-slate-700'
}`}>
{item.status === 'completed' ? 'Fertig' :
item.status === 'in_progress' ? 'In Arbeit' : 'Geplant'}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)
}
// ============================================================================
// Technisch Tab
// ============================================================================
function TechnischTab() {
return (
<div className="space-y-6">
{/* API Endpoints */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">API Endpoints</h3>
<div className="space-y-3">
{[
{ method: 'GET', path: '/api/v1/compliance/regulations', desc: 'Liste aller Verordnungen' },
{ method: 'GET', path: '/api/v1/compliance/controls', desc: 'Control Catalogue' },
{ method: 'PUT', path: '/api/v1/compliance/controls/{id}/review', desc: 'Control Review' },
{ method: 'GET', path: '/api/v1/compliance/evidence', desc: 'Evidence Liste' },
{ method: 'POST', path: '/api/v1/compliance/evidence/upload', desc: 'Evidence Upload' },
{ method: 'GET', path: '/api/v1/compliance/risks', desc: 'Risk Register' },
{ method: 'GET', path: '/api/v1/compliance/risks/matrix', desc: 'Risk Matrix' },
{ method: 'GET', path: '/api/v1/compliance/dashboard', desc: 'Dashboard Stats' },
{ method: 'POST', path: '/api/v1/compliance/export', desc: 'Audit Export erstellen' },
{ method: 'POST', path: '/api/v1/compliance/seed', desc: 'Datenbank seeden' },
].map((ep, idx) => (
<div key={idx} className="flex items-center gap-4 p-3 bg-slate-50 rounded-lg font-mono text-sm">
<span className={`px-2 py-1 rounded text-xs font-bold ${
ep.method === 'GET' ? 'bg-green-100 text-green-700' :
ep.method === 'POST' ? 'bg-blue-100 text-blue-700' :
'bg-yellow-100 text-yellow-700'
}`}>
{ep.method}
</span>
<span className="text-slate-700 flex-1">{ep.path}</span>
<span className="text-slate-500 text-xs">{ep.desc}</span>
</div>
))}
</div>
</div>
{/* Database Schema */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Datenmodell</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{[
{ table: 'compliance_regulations', fields: 'id, code, name, regulation_type, source_url, effective_date' },
{ table: 'compliance_requirements', fields: 'id, regulation_id, article, title, description, is_applicable' },
{ table: 'compliance_controls', fields: 'id, control_id, domain, title, status, is_automated, owner' },
{ table: 'compliance_control_mappings', fields: 'id, requirement_id, control_id, coverage_level' },
{ table: 'compliance_evidence', fields: 'id, control_id, evidence_type, title, artifact_path, status' },
{ table: 'compliance_risks', fields: 'id, risk_id, title, likelihood, impact, inherent_risk, status' },
{ table: 'compliance_audit_exports', fields: 'id, export_type, status, file_path, file_hash' },
].map((t, idx) => (
<div key={idx} className="border rounded-lg p-4">
<h4 className="font-mono font-semibold text-primary-600 mb-2">{t.table}</h4>
<p className="text-xs text-slate-500 font-mono">{t.fields}</p>
</div>
))}
</div>
</div>
{/* Enums */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Enums</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="border rounded-lg p-4">
<h4 className="font-semibold text-slate-900 mb-2">ControlDomainEnum</h4>
<div className="flex flex-wrap gap-1">
{['gov', 'priv', 'iam', 'crypto', 'sdlc', 'ops', 'ai', 'cra', 'aud'].map((d) => (
<span key={d} className="px-2 py-0.5 bg-slate-100 text-slate-700 text-xs rounded">{d}</span>
))}
</div>
</div>
<div className="border rounded-lg p-4">
<h4 className="font-semibold text-slate-900 mb-2">ControlStatusEnum</h4>
<div className="flex flex-wrap gap-1">
{['pass', 'partial', 'fail', 'planned', 'n/a'].map((s) => (
<span key={s} className="px-2 py-0.5 bg-slate-100 text-slate-700 text-xs rounded">{s}</span>
))}
</div>
</div>
<div className="border rounded-lg p-4">
<h4 className="font-semibold text-slate-900 mb-2">RiskLevelEnum</h4>
<div className="flex flex-wrap gap-1">
{['low', 'medium', 'high', 'critical'].map((l) => (
<span key={l} className="px-2 py-0.5 bg-slate-100 text-slate-700 text-xs rounded">{l}</span>
))}
</div>
</div>
</div>
</div>
</div>
)
}
// ============================================================================
// Audit Tab
// ============================================================================
function AuditTab() {
return (
<div className="space-y-6">
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-slate-900">Audit Export</h3>
<Link
href="/admin/compliance/export"
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 text-sm"
>
Export Wizard oeffnen
</Link>
</div>
<p className="text-slate-600 mb-6">
Erstellen Sie ZIP-Pakete mit allen relevanten Compliance-Daten fuer externe Pruefer.
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="border rounded-lg p-4">
<h4 className="font-semibold text-slate-900 mb-2">Vollstaendiger Export</h4>
<p className="text-sm text-slate-600">Alle Daten inkl. Regulations, Controls, Evidence, Risks</p>
</div>
<div className="border rounded-lg p-4">
<h4 className="font-semibold text-slate-900 mb-2">Nur Controls</h4>
<p className="text-sm text-slate-600">Control Catalogue mit Mappings</p>
</div>
<div className="border rounded-lg p-4">
<h4 className="font-semibold text-slate-900 mb-2">Nur Evidence</h4>
<p className="text-sm text-slate-600">Evidence-Dateien und Metadaten</p>
</div>
</div>
</div>
{/* Export Format */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Export Format</h3>
<div className="bg-slate-50 rounded-lg p-4 font-mono text-sm">
<pre className="whitespace-pre overflow-x-auto">{`
audit_export_2026-01-16/
├── index.html # Navigations-Uebersicht
├── summary.json # Maschinenlesbare Zusammenfassung
├── regulations/
│ ├── gdpr.json
│ ├── aiact.json
│ └── ...
├── controls/
│ ├── control_catalogue.json
│ └── control_catalogue.xlsx
├── evidence/
│ ├── scan_reports/
│ ├── policies/
│ └── configs/
├── risks/
│ └── risk_register.json
└── README.md # Erklaerung fuer Pruefer
`}</pre>
</div>
</div>
{/* Audit Quick Links */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Link
href="/admin/compliance/controls"
className="bg-white rounded-xl shadow-sm border p-6 hover:border-primary-500 transition-colors"
>
<h4 className="font-semibold text-slate-900 mb-2">Control Reviews</h4>
<p className="text-sm text-slate-600">Controls ueberpruefen und Status aktualisieren</p>
</Link>
<Link
href="/admin/compliance/evidence"
className="bg-white rounded-xl shadow-sm border p-6 hover:border-primary-500 transition-colors"
>
<h4 className="font-semibold text-slate-900 mb-2">Evidence Management</h4>
<p className="text-sm text-slate-600">Nachweise hochladen und verwalten</p>
</Link>
</div>
</div>
)
}
// ============================================================================
// Dokumentation Tab
// ============================================================================
function DokumentationTab() {
return (
<div className="space-y-6">
{/* Quick Start Guide */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Quick Start Guide</h3>
<div className="space-y-4">
<div className="flex gap-4">
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">1</div>
<div>
<p className="font-medium text-slate-900">Datenbank initialisieren</p>
<p className="text-sm text-slate-600">Klicken Sie auf "Datenbank initialisieren" im Dashboard, um die Seed-Daten zu laden.</p>
</div>
</div>
<div className="flex gap-4">
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">2</div>
<div>
<p className="font-medium text-slate-900">Controls reviewen</p>
<p className="text-sm text-slate-600">Gehen Sie zum Control Catalogue und bewerten Sie den Status jedes Controls.</p>
</div>
</div>
<div className="flex gap-4">
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">3</div>
<div>
<p className="font-medium text-slate-900">Evidence hochladen</p>
<p className="text-sm text-slate-600">Laden Sie Nachweise (Scan-Reports, Policies, Screenshots) fuer Ihre Controls hoch.</p>
</div>
</div>
<div className="flex gap-4">
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">4</div>
<div>
<p className="font-medium text-slate-900">Risiken bewerten</p>
<p className="text-sm text-slate-600">Dokumentieren Sie identifizierte Risiken in der Risk Matrix.</p>
</div>
</div>
<div className="flex gap-4">
<div className="w-8 h-8 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center font-bold flex-shrink-0">5</div>
<div>
<p className="font-medium text-slate-900">Audit Export</p>
<p className="text-sm text-slate-600">Generieren Sie ein ZIP-Paket fuer externe Pruefer.</p>
</div>
</div>
</div>
</div>
{/* Regulatory Framework */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Abgedeckte Verordnungen</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 className="font-medium text-slate-900 mb-2">EU-Verordnungen & Richtlinien</h4>
<ul className="text-sm text-slate-600 space-y-1">
<li>- DSGVO (Datenschutz-Grundverordnung)</li>
<li>- AI Act (KI-Verordnung)</li>
<li>- CRA (Cyber Resilience Act)</li>
<li>- NIS2 (Netzwerk- und Informationssicherheit)</li>
<li>- DSA (Digital Services Act)</li>
<li>- Data Act (Datenverordnung)</li>
<li>- DGA (Data Governance Act)</li>
<li>- ePrivacy-Richtlinie</li>
</ul>
</div>
<div>
<h4 className="font-medium text-slate-900 mb-2">Deutsche Standards</h4>
<ul className="text-sm text-slate-600 space-y-1">
<li>- BSI-TR-03161-1 (Mobile Anwendungen Teil 1)</li>
<li>- BSI-TR-03161-2 (Mobile Anwendungen Teil 2)</li>
<li>- BSI-TR-03161-3 (Mobile Anwendungen Teil 3)</li>
<li>- TDDDG (Telekommunikation-Digitale-Dienste-Datenschutz)</li>
</ul>
</div>
</div>
</div>
{/* Control Domains */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Control Domains</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{Object.entries(DOMAIN_LABELS).map(([key, label]) => (
<div key={key} className="border rounded-lg p-4">
<span className="font-mono text-xs text-primary-600 bg-primary-50 px-2 py-0.5 rounded">{key}</span>
<p className="font-medium text-slate-900 mt-2">{label}</p>
</div>
))}
</div>
</div>
{/* Links */}
<div className="bg-white rounded-xl shadow-sm border p-6">
<h3 className="text-lg font-semibold text-slate-900 mb-4">Externe Ressourcen</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<a
href="https://eur-lex.europa.eu/eli/reg/2016/679/oj/eng"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 p-3 border rounded-lg hover:bg-slate-50"
>
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
<span className="text-sm text-slate-700">DSGVO - EUR-Lex</span>
</a>
<a
href="https://eur-lex.europa.eu/eli/reg/2024/1689/oj/eng"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 p-3 border rounded-lg hover:bg-slate-50"
>
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
<span className="text-sm text-slate-700">AI Act - EUR-Lex</span>
</a>
<a
href="https://eur-lex.europa.eu/eli/reg/2024/2847/oj/eng"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 p-3 border rounded-lg hover:bg-slate-50"
>
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
<span className="text-sm text-slate-700">CRA - EUR-Lex</span>
</a>
<a
href="https://www.bsi.bund.de/DE/Themen/Unternehmen-und-Organisationen/Standards-und-Zertifizierung/Technische-Richtlinien/TR-nach-Thema-sortiert/tr03161/tr-03161.html"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 p-3 border rounded-lg hover:bg-slate-50"
>
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
<span className="text-sm text-slate-700">BSI-TR-03161 - BSI</span>
</a>
</div>
</div>
</div>
)
}