'use client' import React, { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import { useSDK, ServiceModule } from '@/lib/sdk' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' // ============================================================================= // TYPES // ============================================================================= type ModuleCategory = 'gdpr' | 'ai-act' | 'iso27001' | 'nis2' | 'custom' type ModuleStatus = 'active' | 'inactive' | 'pending' interface DisplayModule extends ServiceModule { category: ModuleCategory status: ModuleStatus requirementsCount: number controlsCount: number completionPercent: number } interface BackendModule { id: string name: string display_name: string description: string service_type: string | null processes_pii: boolean ai_components: boolean criticality: string is_active: boolean compliance_score: number | null regulation_count: number risk_count: number } // ============================================================================= // FALLBACK MODULES (used when backend is unavailable) // ============================================================================= const fallbackModules: Omit[] = [ { id: 'mod-gdpr', name: 'DSGVO Compliance', description: 'Datenschutz-Grundverordnung - Vollstaendige Umsetzung aller Anforderungen', category: 'gdpr', regulations: ['DSGVO', 'BDSG'], criticality: 'HIGH', processesPersonalData: true, hasAIComponents: false, requirementsCount: 45, controlsCount: 32, }, { id: 'mod-ai-act', name: 'AI Act Compliance', description: 'EU AI Act - Klassifizierung und Anforderungen fuer KI-Systeme', category: 'ai-act', regulations: ['EU AI Act'], criticality: 'HIGH', processesPersonalData: false, hasAIComponents: true, requirementsCount: 28, controlsCount: 18, }, { id: 'mod-iso27001', name: 'ISO 27001', description: 'Informationssicherheits-Managementsystem nach ISO/IEC 27001', category: 'iso27001', regulations: ['ISO 27001', 'ISO 27002'], criticality: 'MEDIUM', processesPersonalData: false, hasAIComponents: false, requirementsCount: 114, controlsCount: 93, }, { id: 'mod-nis2', name: 'NIS2 Richtlinie', description: 'Netz- und Informationssicherheit fuer kritische Infrastrukturen', category: 'nis2', regulations: ['NIS2'], criticality: 'HIGH', processesPersonalData: false, hasAIComponents: false, requirementsCount: 36, controlsCount: 24, }, ] // ============================================================================= // HELPERS // ============================================================================= function categorizeModule(name: string): ModuleCategory { const lower = name.toLowerCase() if (lower.includes('dsgvo') || lower.includes('gdpr') || lower.includes('datenschutz')) return 'gdpr' if (lower.includes('ai act') || lower.includes('ki-verordnung')) return 'ai-act' if (lower.includes('iso 27001') || lower.includes('iso27001') || lower.includes('isms')) return 'iso27001' if (lower.includes('nis2') || lower.includes('netz- und informations')) return 'nis2' return 'custom' } function mapBackendToDisplay(m: BackendModule): Omit { return { id: m.id, name: m.display_name || m.name, description: m.description || '', category: categorizeModule(m.display_name || m.name), regulations: [], criticality: (m.criticality || 'MEDIUM').toUpperCase(), processesPersonalData: m.processes_pii, hasAIComponents: m.ai_components, requirementsCount: m.regulation_count || 0, controlsCount: m.risk_count || 0, } } // ============================================================================= // COMPONENTS // ============================================================================= function ModuleCard({ module, isActive, onActivate, onDeactivate, onConfigure, }: { module: DisplayModule isActive: boolean onActivate: () => void onDeactivate: () => void onConfigure: () => void }) { const categoryColors = { gdpr: 'bg-blue-100 text-blue-700', 'ai-act': 'bg-purple-100 text-purple-700', iso27001: 'bg-green-100 text-green-700', nis2: 'bg-orange-100 text-orange-700', custom: 'bg-gray-100 text-gray-700', } const statusColors = { active: 'bg-green-100 text-green-700', inactive: 'bg-gray-100 text-gray-500', pending: 'bg-yellow-100 text-yellow-700', } return (
{module.category.toUpperCase()} {module.status === 'active' ? 'Aktiv' : module.status === 'pending' ? 'Ausstehend' : 'Inaktiv'} {module.hasAIComponents && ( KI )}

{module.name}

{module.description}

{module.regulations.length > 0 && (
{module.regulations.map(reg => ( {reg} ))}
)}
Anforderungen: {module.requirementsCount}
Kontrollen: {module.controlsCount}
Fortschritt {module.completionPercent}%
{isActive ? ( <> ) : ( )}
) } // ============================================================================= // MAIN PAGE // ============================================================================= export default function ModulesPage() { const { state, dispatch } = useSDK() const router = useRouter() const [filter, setFilter] = useState('all') const [availableModules, setAvailableModules] = useState[]>(fallbackModules) const [isLoadingModules, setIsLoadingModules] = useState(true) const [backendError, setBackendError] = useState(null) const [showCreateModal, setShowCreateModal] = useState(false) const [newModuleName, setNewModuleName] = useState('') const [newModuleCategory, setNewModuleCategory] = useState('custom') const [newModuleDescription, setNewModuleDescription] = useState('') const [actionError, setActionError] = useState(null) // Load modules from backend useEffect(() => { async function loadModules() { try { const response = await fetch('/api/sdk/v1/modules') if (response.ok) { const data = await response.json() if (data.modules && data.modules.length > 0) { const mapped = data.modules.map(mapBackendToDisplay) setAvailableModules(mapped) setBackendError(null) } } else { setBackendError('Backend nicht erreichbar — zeige Standard-Module') } } catch { setBackendError('Backend nicht erreichbar — zeige Standard-Module') } finally { setIsLoadingModules(false) } } loadModules() }, []) // Convert SDK modules to display modules with additional UI properties const displayModules: DisplayModule[] = availableModules.map(template => { const activeModule = state.modules.find(m => m.id === template.id) const isActive = !!activeModule // Calculate completion based on linked requirements and controls const linkedRequirements = state.requirements.filter(r => r.applicableModules.includes(template.id) ) const completedRequirements = linkedRequirements.filter( r => r.status === 'IMPLEMENTED' || r.status === 'VERIFIED' ) const completionPercent = linkedRequirements.length > 0 ? Math.round((completedRequirements.length / linkedRequirements.length) * 100) : 0 return { ...template, status: isActive ? 'active' as ModuleStatus : 'inactive' as ModuleStatus, completionPercent, } }) const filteredModules = filter === 'all' ? displayModules : displayModules.filter(m => m.category === filter || m.status === filter) const activeModulesCount = state.modules.length const totalRequirements = displayModules .filter(m => state.modules.some(sm => sm.id === m.id)) .reduce((sum, m) => sum + m.requirementsCount, 0) const totalControls = displayModules .filter(m => state.modules.some(sm => sm.id === m.id)) .reduce((sum, m) => sum + m.controlsCount, 0) const handleActivateModule = async (module: DisplayModule) => { const serviceModule: ServiceModule = { id: module.id, name: module.name, description: module.description, regulations: module.regulations, criticality: module.criticality, processesPersonalData: module.processesPersonalData, hasAIComponents: module.hasAIComponents, } dispatch({ type: 'ADD_MODULE', payload: serviceModule }) setActionError(null) try { const res = await fetch(`/api/sdk/v1/modules/${encodeURIComponent(module.id)}/activate`, { method: 'POST', }) if (!res.ok) throw new Error('Aktivierung fehlgeschlagen') } catch { // Rollback optimistic update const rollbackModules = state.modules.filter(m => m.id !== module.id) dispatch({ type: 'SET_STATE', payload: { modules: rollbackModules } }) setActionError(`Modul "${module.name}" konnte nicht aktiviert werden.`) setTimeout(() => setActionError(null), 5000) } } const handleDeactivateModule = async (moduleId: string) => { const previousModules = [...state.modules] const updatedModules = state.modules.filter(m => m.id !== moduleId) dispatch({ type: 'SET_STATE', payload: { modules: updatedModules } }) setActionError(null) try { const res = await fetch(`/api/sdk/v1/modules/${encodeURIComponent(moduleId)}/deactivate`, { method: 'POST', }) if (!res.ok) throw new Error('Deaktivierung fehlgeschlagen') } catch { // Rollback optimistic update dispatch({ type: 'SET_STATE', payload: { modules: previousModules } }) setActionError('Modul konnte nicht deaktiviert werden.') setTimeout(() => setActionError(null), 5000) } } const handleCreateModule = async () => { if (!newModuleName.trim()) return try { const res = await fetch('/api/sdk/v1/modules', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newModuleName, category: newModuleCategory, description: newModuleDescription, }), }) if (res.ok) { const data = await res.json() const newMod: Omit = { id: data.id || `custom-${Date.now()}`, name: newModuleName, description: newModuleDescription, category: newModuleCategory, regulations: [], criticality: 'MEDIUM', processesPersonalData: false, hasAIComponents: false, requirementsCount: 0, controlsCount: 0, } setAvailableModules(prev => [...prev, newMod]) } setShowCreateModal(false) setNewModuleName('') setNewModuleCategory('custom') setNewModuleDescription('') } catch { setActionError('Modul konnte nicht erstellt werden.') setTimeout(() => setActionError(null), 5000) } } const stepInfo = STEP_EXPLANATIONS['modules'] return (
{/* Step Header */} {/* Error Toast */} {actionError && (

{actionError}

)} {/* Create Module Modal */} {showCreateModal && (

Eigenes Modul erstellen

setNewModuleName(e.target.value)} placeholder="z.B. ISO 42001 AI Management" className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />