'use client' /** * DataFlowDiagram - Visual representation of module dependencies * * Shows how backend services, modules, and frontend pages are connected. * Uses SVG for rendering connections. */ import { useState, useRef, useEffect } from 'react' import { MODULE_REGISTRY, type BackendModule } from '@/lib/module-registry' interface NodePosition { x: number y: number width: number height: number } interface ServiceGroup { name: string port: number modules: BackendModule[] } const SERVICE_COLORS: Record = { 'consent-service': '#8b5cf6', // purple 'python-backend': '#f59e0b', // amber 'klausur-service': '#10b981', // emerald 'voice-service': '#3b82f6', // blue } const STATUS_COLORS = { connected: '#22c55e', partial: '#eab308', 'not-connected': '#ef4444', deprecated: '#6b7280' } export function DataFlowDiagram() { const [selectedModule, setSelectedModule] = useState(null) const [hoveredModule, setHoveredModule] = useState(null) const [showLegend, setShowLegend] = useState(true) const containerRef = useRef(null) // Group modules by backend service const serviceGroups: ServiceGroup[] = [] const seenServices = new Set() MODULE_REGISTRY.forEach(module => { if (!seenServices.has(module.backend.service)) { seenServices.add(module.backend.service) serviceGroups.push({ name: module.backend.service, port: module.backend.port, modules: MODULE_REGISTRY.filter(m => m.backend.service === module.backend.service) }) } }) // Calculate positions const serviceWidth = 280 const serviceSpacing = 40 const moduleHeight = 60 const moduleSpacing = 10 const headerHeight = 50 const padding = 20 const totalWidth = serviceGroups.length * serviceWidth + (serviceGroups.length - 1) * serviceSpacing + padding * 2 const maxModulesInService = Math.max(...serviceGroups.map(s => s.modules.length)) const totalHeight = headerHeight + maxModulesInService * (moduleHeight + moduleSpacing) + padding * 2 + 100 // Get connections between modules (dependencies) const connections: { from: string; to: string }[] = [] MODULE_REGISTRY.forEach(module => { if (module.dependencies) { module.dependencies.forEach(dep => { connections.push({ from: module.id, to: dep }) }) } }) // Get module position const getModulePosition = (moduleId: string): NodePosition | null => { let serviceIndex = 0 for (const service of serviceGroups) { const moduleIndex = service.modules.findIndex(m => m.id === moduleId) if (moduleIndex !== -1) { return { x: padding + serviceIndex * (serviceWidth + serviceSpacing) + serviceWidth / 2, y: headerHeight + moduleIndex * (moduleHeight + moduleSpacing) + moduleHeight / 2 + 40, width: serviceWidth - 40, height: moduleHeight } } serviceIndex++ } return null } // Check if module is related to selected/hovered module const isRelated = (moduleId: string): boolean => { const target = selectedModule || hoveredModule if (!target) return false // Direct match if (moduleId === target) return true // Check dependencies const targetModule = MODULE_REGISTRY.find(m => m.id === target) if (targetModule?.dependencies?.includes(moduleId)) return true // Check reverse dependencies const module = MODULE_REGISTRY.find(m => m.id === moduleId) if (module?.dependencies?.includes(target)) return true return false } return (
{/* Controls */}

Datenfluss-Diagramm

{selectedModule && ( )}
{/* Legend */} {showLegend && (
Services: {Object.entries(SERVICE_COLORS).map(([service, color]) => (
{service}
))} Status:
Verbunden
Teilweise
Nicht verbunden
)} {/* Diagram */}
{/* Arrow marker */} {/* Background Grid */} {/* Service Groups */} {serviceGroups.map((service, serviceIdx) => { const x = padding + serviceIdx * (serviceWidth + serviceSpacing) const serviceColor = SERVICE_COLORS[service.name] || '#6b7280' return ( {/* Service Container */} {/* Service Header */} {service.name} Port {service.port} {/* Modules */} {service.modules.map((module, moduleIdx) => { const moduleX = x + 20 const moduleY = padding + headerHeight + 20 + moduleIdx * (moduleHeight + moduleSpacing) const isSelected = selectedModule === module.id const isHovered = hoveredModule === module.id const related = isRelated(module.id) const statusColor = STATUS_COLORS[module.frontend.status] const opacity = (selectedModule || hoveredModule) ? (related ? 1 : 0.3) : 1 return ( setSelectedModule(isSelected ? null : module.id)} onMouseEnter={() => setHoveredModule(module.id)} onMouseLeave={() => setHoveredModule(null)} style={{ cursor: 'pointer', opacity }} className="transition-opacity duration-200" > {/* Module Box */} {/* Status Indicator */} {/* Module Name */} {module.name.length > 25 ? module.name.slice(0, 25) + '...' : module.name} {/* Module ID */} {module.id} {/* Priority Badge */} {module.priority.toUpperCase()} {/* Dependency indicator */} {module.dependencies && module.dependencies.length > 0 && ( {module.dependencies.length} )} ) })} ) })} {/* Connections (Dependencies) */} {connections.map((conn, idx) => { const fromPos = getModulePosition(conn.from) const toPos = getModulePosition(conn.to) if (!fromPos || !toPos) return null const isHighlighted = (selectedModule || hoveredModule) && (conn.from === (selectedModule || hoveredModule) || conn.to === (selectedModule || hoveredModule)) const opacity = (selectedModule || hoveredModule) ? (isHighlighted ? 1 : 0.1) : 0.4 // Calculate curved path const startX = fromPos.x const startY = fromPos.y const endX = toPos.x const endY = toPos.y const midX = (startX + endX) / 2 const controlOffset = Math.abs(startX - endX) * 0.3 const path = startX < endX ? `M ${startX + fromPos.width / 2} ${startY} C ${startX + fromPos.width / 2 + controlOffset} ${startY}, ${endX - toPos.width / 2 - controlOffset} ${endY}, ${endX - toPos.width / 2} ${endY}` : `M ${startX - fromPos.width / 2} ${startY} C ${startX - fromPos.width / 2 - controlOffset} ${startY}, ${endX + toPos.width / 2 + controlOffset} ${endY}, ${endX + toPos.width / 2} ${endY}` return ( ) })} {/* Frontend Layer (Bottom) */} Admin v2 Frontend (Next.js - Port 3002) /compliance | /ai | /infrastructure | /education | /communication | /development
{/* Selected Module Details */} {selectedModule && (

{MODULE_REGISTRY.find(m => m.id === selectedModule)?.name}

ID: {selectedModule}

{MODULE_REGISTRY.find(m => m.id === selectedModule)?.dependencies && (

Abhaengigkeiten: {MODULE_REGISTRY.find(m => m.id === selectedModule)?.dependencies?.map(dep => ( ))}

)} {MODULE_REGISTRY.find(m => m.id === selectedModule)?.frontend.adminV2Page && (

Frontend: m.id === selectedModule)?.frontend.adminV2Page} className="ml-2 text-purple-600 hover:underline" > {MODULE_REGISTRY.find(m => m.id === selectedModule)?.frontend.adminV2Page}

)}
)}
) }