Files
breakpilot-compliance/admin-compliance/app/(admin)/development/screen-flow/page.tsx
2026-02-12 20:29:27 +01:00

623 lines
27 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
/**
* Screen Flow Visualization - Compliance SDK
*
* Visualisiert alle Screens aus:
* - Admin Compliance (Port 3007): Verwaltung
* - SDK Pipeline: Compliance-Module
*/
import { useCallback, useState, useMemo, useEffect } from 'react'
import ReactFlow, {
Node,
Edge,
Controls,
Background,
MiniMap,
useNodesState,
useEdgesState,
BackgroundVariant,
MarkerType,
Panel,
} from 'reactflow'
import 'reactflow/dist/style.css'
// ============================================
// TYPES
// ============================================
interface ScreenDefinition {
id: string
name: string
description: string
category: string
icon: string
url?: string
}
interface ConnectionDef {
source: string
target: string
label?: string
}
// ============================================
// ADMIN COMPLIANCE SCREENS (Port 3007)
// ============================================
const SCREENS: ScreenDefinition[] = [
// === DASHBOARD & VERWALTUNG (Blue) ===
{ id: 'dashboard', name: 'Dashboard', description: 'Uebersicht & Statistiken', category: 'dashboard', icon: '🏠', url: '/dashboard' },
{ id: 'catalog-manager', name: 'Katalogverwaltung', description: 'SDK-Kataloge & Auswahltabellen', category: 'dashboard', icon: '📦', url: '/dashboard/catalog-manager' },
// === DSGVO-GRUNDLAGEN (Violet) ===
{ id: 'vvt', name: 'VVT', description: 'Verarbeitungsverzeichnis Art. 30', category: 'dsgvo', icon: '📋', url: '/sdk/vvt' },
{ id: 'dsfa', name: 'DSFA', description: 'Datenschutz-Folgenabschaetzung', category: 'dsgvo', icon: '⚖️', url: '/sdk/dsfa' },
{ id: 'tom', name: 'TOMs', description: 'Technische & Org. Massnahmen', category: 'dsgvo', icon: '🛡️', url: '/sdk/tom' },
{ id: 'tom-generator', name: 'TOM Generator', description: 'TOM-Erstellung mit Wizard', category: 'dsgvo', icon: '⚙️', url: '/sdk/tom-generator' },
{ id: 'loeschfristen', name: 'Loeschfristen', description: 'Aufbewahrung & Deadlines', category: 'dsgvo', icon: '🗑️', url: '/sdk/loeschfristen' },
{ id: 'einwilligungen', name: 'Einwilligungen', description: 'Nutzer-Consent Uebersicht', category: 'dsgvo', icon: '✅', url: '/sdk/einwilligungen' },
{ id: 'dsr', name: 'Datenschutzanfragen', description: 'DSGVO Art. 15-21 (DSR)', category: 'dsgvo', icon: '🔒', url: '/sdk/dsr' },
{ id: 'consent', name: 'Consent Verwaltung', description: 'Rechtliche Dokumente & Versionen', category: 'dsgvo', icon: '📄', url: '/sdk/consent' },
{ id: 'consent-management', name: 'Consent Management', description: 'Einwilligungsmanagement', category: 'dsgvo', icon: '📝', url: '/sdk/consent-management' },
// === COMPLIANCE-MANAGEMENT (Purple) ===
{ id: 'compliance-hub', name: 'Compliance Hub', description: 'Zentrales GRC Dashboard', category: 'compliance', icon: '✅', url: '/sdk/compliance-hub' },
{ id: 'compliance-scope', name: 'Compliance Scope', description: 'Geltungsbereich definieren', category: 'compliance', icon: '🎯', url: '/sdk/compliance-scope' },
{ id: 'requirements', name: 'Requirements', description: '558+ aus 19 Verordnungen', category: 'compliance', icon: '📜', url: '/sdk/requirements' },
{ id: 'controls', name: 'Controls', description: '474 Control-Mappings', category: 'compliance', icon: '🎛️', url: '/sdk/controls' },
{ id: 'evidence', name: 'Evidence', description: 'Nachweise & Dokumentation', category: 'compliance', icon: '📎', url: '/sdk/evidence' },
{ id: 'risks', name: 'Risiken', description: 'Risk Matrix & Register', category: 'compliance', icon: '⚠️', url: '/sdk/risks' },
{ id: 'audit-checklist', name: 'Audit Checkliste', description: '476 Anforderungen pruefen', category: 'compliance', icon: '📋', url: '/sdk/audit-checklist' },
{ id: 'audit-report', name: 'Audit Report', description: 'PDF Audit-Berichte', category: 'compliance', icon: '📊', url: '/sdk/audit-report' },
{ id: 'workflow', name: 'Workflow', description: 'Freigabe-Workflows', category: 'compliance', icon: '🔄', url: '/sdk/workflow' },
{ id: 'modules', name: 'Service Registry', description: '30+ Service-Module', category: 'compliance', icon: '🔧', url: '/sdk/modules' },
// === KI & AUTOMATISIERUNG (Teal) ===
{ id: 'ai-act', name: 'EU-AI-Act', description: 'KI-Risikoklassifizierung', category: 'ai', icon: '🤖', url: '/sdk/ai-act' },
{ id: 'screening', name: 'Screening', description: 'Compliance-Screening & Pruefung', category: 'ai', icon: '🔍', url: '/sdk/screening' },
{ id: 'rag', name: 'Daten & RAG', description: 'Training Data & RAG', category: 'ai', icon: '🗄️', url: '/sdk/rag' },
{ id: 'quality', name: 'Qualitaet & Audit', description: 'Compliance-Audit & Traceability', category: 'ai', icon: '✨', url: '/sdk/quality' },
{ id: 'advisory-board', name: 'Advisory Board', description: 'KI-Use-Case Pruefung', category: 'ai', icon: '🧑‍⚖️', url: '/sdk/advisory-board' },
{ id: 'obligations', name: 'Pflichten', description: 'NIS2, DSGVO, AI Act', category: 'ai', icon: '⚡', url: '/sdk/obligations' },
{ id: 'escalations', name: 'Eskalations-Queue', description: 'DSB Review & Freigabe', category: 'ai', icon: '🚨', url: '/sdk/escalations' },
// === DOKUMENTE & LEGAL (Orange) ===
{ id: 'document-generator', name: 'Document Generator', description: 'Datenschutz-Dokumente erstellen', category: 'documents', icon: '📄', url: '/sdk/document-generator' },
{ id: 'notfallplan', name: 'Notfallplan', description: 'Incident Response Plan', category: 'documents', icon: '🚨', url: '/sdk/notfallplan' },
{ id: 'source-policy', name: 'Quellen-Policy', description: 'Datenquellen & Compliance', category: 'documents', icon: '📚', url: '/sdk/source-policy' },
{ id: 'cookie-banner', name: 'Cookie-Banner', description: 'Cookie Consent Builder', category: 'documents', icon: '🍪', url: '/sdk/cookie-banner' },
{ id: 'company-profile', name: 'Unternehmensprofil', description: 'Firmen-Stammdaten', category: 'documents', icon: '🏢', url: '/sdk/company-profile' },
{ id: 'security-backlog', name: 'Security Backlog', description: 'Sicherheits-Massnahmen Tracking', category: 'documents', icon: '🔐', url: '/sdk/security-backlog' },
// === VENDOR & EXTERN (Green) ===
{ id: 'vendor-compliance', name: 'Vendor Compliance', description: 'Lieferanten-Management', category: 'vendor', icon: '🏭', url: '/sdk/vendor-compliance' },
{ id: 'vendor-vendors', name: 'Vendor-Liste', description: 'Lieferanten-Uebersicht', category: 'vendor', icon: '📋', url: '/sdk/vendor-compliance/vendors' },
{ id: 'vendor-contracts', name: 'Vertraege', description: 'AVV & Vertragsmanagement', category: 'vendor', icon: '📝', url: '/sdk/vendor-compliance/contracts' },
{ id: 'vendor-controls', name: 'Vendor Controls', description: 'Lieferanten-Kontrollen', category: 'vendor', icon: '🎛️', url: '/sdk/vendor-compliance/controls' },
{ id: 'vendor-risks', name: 'Vendor Risiken', description: 'Lieferanten-Risikobewertung', category: 'vendor', icon: '⚠️', url: '/sdk/vendor-compliance/risks' },
{ id: 'vendor-processing', name: 'Verarbeitungen', description: 'Auftragsverarbeitung', category: 'vendor', icon: '🔄', url: '/sdk/vendor-compliance/processing-activities' },
{ id: 'vendor-reports', name: 'Vendor Reports', description: 'Lieferanten-Berichte', category: 'vendor', icon: '📊', url: '/sdk/vendor-compliance/reports' },
{ id: 'dsms', name: 'DSMS', description: 'Datenschutz-Management-System', category: 'vendor', icon: '🏛️', url: '/sdk/dsms' },
{ id: 'import', name: 'Import', description: 'Daten-Import', category: 'vendor', icon: '📥', url: '/sdk/import' },
// === ENTWICKLUNG (Slate) ===
{ id: 'dev-docs', name: 'Developer Docs', description: 'API & Architektur', category: 'development', icon: '📖', url: '/development/docs' },
{ id: 'dev-screen-flow', name: 'Screen Flow', description: 'UI Screen-Verbindungen', category: 'development', icon: '🔀', url: '/development/screen-flow' },
{ id: 'dev-brandbook', name: 'Brandbook', description: 'Corporate Design', category: 'development', icon: '🎨', url: '/development/brandbook' },
]
const CONNECTIONS: ConnectionDef[] = [
// === DASHBOARD FLOW ===
{ source: 'dashboard', target: 'catalog-manager', label: 'Kataloge' },
{ source: 'dashboard', target: 'compliance-hub', label: 'Compliance' },
{ source: 'dashboard', target: 'vvt', label: 'VVT' },
// === DSGVO FLOW ===
{ source: 'consent-management', target: 'einwilligungen', label: 'Nutzer' },
{ source: 'consent-management', target: 'dsr' },
{ source: 'consent', target: 'consent-management' },
{ source: 'consent', target: 'cookie-banner' },
{ source: 'dsr', target: 'loeschfristen' },
{ source: 'vvt', target: 'tom' },
{ source: 'vvt', target: 'dsfa' },
{ source: 'dsfa', target: 'tom' },
{ source: 'tom', target: 'tom-generator', label: 'Wizard' },
{ source: 'einwilligungen', target: 'consent' },
{ source: 'einwilligungen', target: 'loeschfristen' },
// === COMPLIANCE FLOW ===
{ source: 'compliance-hub', target: 'audit-checklist', label: 'Audit' },
{ source: 'compliance-hub', target: 'requirements', label: 'Anforderungen' },
{ source: 'compliance-hub', target: 'risks', label: 'Risiken' },
{ source: 'compliance-hub', target: 'ai-act', label: 'AI Act' },
{ source: 'compliance-hub', target: 'compliance-scope' },
{ source: 'requirements', target: 'controls' },
{ source: 'controls', target: 'evidence' },
{ source: 'audit-checklist', target: 'audit-report', label: 'Report' },
{ source: 'risks', target: 'controls' },
{ source: 'modules', target: 'controls' },
{ source: 'obligations', target: 'requirements' },
{ source: 'dsms', target: 'workflow' },
{ source: 'workflow', target: 'audit-report' },
// === KI & AUTOMATISIERUNG FLOW ===
{ source: 'advisory-board', target: 'escalations', label: 'Eskalation' },
{ source: 'advisory-board', target: 'dsfa', label: 'Risiko' },
{ source: 'ai-act', target: 'screening' },
{ source: 'screening', target: 'advisory-board' },
{ source: 'source-policy', target: 'rag' },
{ source: 'rag', target: 'quality' },
// === DOKUMENTE FLOW ===
{ source: 'document-generator', target: 'notfallplan' },
{ source: 'document-generator', target: 'audit-report' },
{ source: 'security-backlog', target: 'tom' },
{ source: 'company-profile', target: 'document-generator' },
// === VENDOR FLOW ===
{ source: 'vendor-compliance', target: 'vendor-vendors' },
{ source: 'vendor-compliance', target: 'vendor-contracts' },
{ source: 'vendor-compliance', target: 'vendor-controls' },
{ source: 'vendor-compliance', target: 'vendor-risks' },
{ source: 'vendor-compliance', target: 'vendor-processing' },
{ source: 'vendor-compliance', target: 'vendor-reports' },
{ source: 'vendor-vendors', target: 'vendor-contracts' },
{ source: 'vendor-risks', target: 'risks' },
{ source: 'dsms', target: 'compliance-hub' },
{ source: 'import', target: 'catalog-manager' },
// === ENTWICKLUNG FLOW ===
{ source: 'dev-brandbook', target: 'dev-screen-flow' },
{ source: 'dev-docs', target: 'dev-brandbook' },
]
// ============================================
// CATEGORY COLORS & LABELS
// ============================================
const COLORS: Record<string, { bg: string; border: string; text: string }> = {
dashboard: { bg: '#dbeafe', border: '#3b82f6', text: '#1e40af' },
dsgvo: { bg: '#ede9fe', border: '#7c3aed', text: '#5b21b6' },
compliance: { bg: '#f3e8ff', border: '#9333ea', text: '#6b21a8' },
ai: { bg: '#ccfbf1', border: '#14b8a6', text: '#0f766e' },
documents: { bg: '#ffedd5', border: '#f97316', text: '#c2410c' },
vendor: { bg: '#dcfce7', border: '#22c55e', text: '#166534' },
development: { bg: '#f1f5f9', border: '#64748b', text: '#334155' },
}
const LABELS: Record<string, string> = {
dashboard: 'Dashboard & Verwaltung',
dsgvo: 'DSGVO-Grundlagen',
compliance: 'Compliance-Management',
ai: 'KI & Automatisierung',
documents: 'Dokumente & Legal',
vendor: 'Vendor & Extern',
development: 'Entwicklung',
}
// ============================================
// HELPER: Find all connected nodes
// ============================================
function findConnectedNodes(
startNodeId: string,
connections: ConnectionDef[],
direction: 'children' | 'parents' | 'both' = 'children'
): Set<string> {
const connected = new Set<string>()
connected.add(startNodeId)
const queue = [startNodeId]
while (queue.length > 0) {
const current = queue.shift()!
connections.forEach(conn => {
if ((direction === 'children' || direction === 'both') && conn.source === current) {
if (!connected.has(conn.target)) {
connected.add(conn.target)
queue.push(conn.target)
}
}
if ((direction === 'parents' || direction === 'both') && conn.target === current) {
if (!connected.has(conn.source)) {
connected.add(conn.source)
queue.push(conn.source)
}
}
})
}
return connected
}
// ============================================
// LAYOUT HELPERS
// ============================================
const CATEGORY_POSITIONS: Record<string, { x: number; y: number }> = {
dashboard: { x: 400, y: 30 },
dsgvo: { x: 50, y: 150 },
compliance: { x: 700, y: 150 },
ai: { x: 50, y: 380 },
documents: { x: 400, y: 380 },
vendor: { x: 700, y: 380 },
development: { x: 400, y: 580 },
}
const getNodePosition = (id: string, category: string) => {
const base = CATEGORY_POSITIONS[category] || { x: 400, y: 300 }
const categoryScreens = SCREENS.filter(s => s.category === category)
const categoryIndex = categoryScreens.findIndex(s => s.id === id)
const cols = Math.ceil(Math.sqrt(categoryScreens.length + 1))
const row = Math.floor(categoryIndex / cols)
const col = categoryIndex % cols
return {
x: base.x + col * 160,
y: base.y + row * 90,
}
}
// ============================================
// MAIN COMPONENT
// ============================================
export default function ScreenFlowPage() {
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
const [selectedNode, setSelectedNode] = useState<string | null>(null)
const [previewScreen, setPreviewScreen] = useState<ScreenDefinition | null>(null)
const baseUrl = 'https://macmini:3007'
// Calculate connected nodes
const connectedNodes = useMemo(() => {
if (!selectedNode) return new Set<string>()
return findConnectedNodes(selectedNode, CONNECTIONS, 'children')
}, [selectedNode])
// Create nodes with useMemo
const initialNodes = useMemo((): Node[] => {
return SCREENS.map((screen) => {
const catColors = COLORS[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
const position = getNodePosition(screen.id, screen.category)
let opacity = 1
if (selectedNode) {
opacity = connectedNodes.has(screen.id) ? 1 : 0.2
} else if (selectedCategory) {
opacity = screen.category === selectedCategory ? 1 : 0.2
}
const isSelected = selectedNode === screen.id
return {
id: screen.id,
type: 'default',
position,
data: {
label: (
<div className="text-center p-1">
<div className="text-lg mb-1">{screen.icon}</div>
<div className="font-medium text-xs leading-tight">{screen.name}</div>
</div>
),
},
style: {
background: isSelected ? catColors.border : catColors.bg,
color: isSelected ? 'white' : catColors.text,
border: `2px solid ${catColors.border}`,
borderRadius: '12px',
padding: '6px',
minWidth: '110px',
opacity,
cursor: 'pointer',
boxShadow: isSelected ? `0 0 20px ${catColors.border}` : 'none',
},
}
})
}, [selectedCategory, selectedNode, connectedNodes])
// Create edges with useMemo
const initialEdges = useMemo((): Edge[] => {
return CONNECTIONS.map((conn, index) => {
const isHighlighted = selectedNode && (conn.source === selectedNode || conn.target === selectedNode)
const isInSubtree = selectedNode && connectedNodes.has(conn.source) && connectedNodes.has(conn.target)
return {
id: `e-${conn.source}-${conn.target}-${index}`,
source: conn.source,
target: conn.target,
label: conn.label,
type: 'smoothstep',
animated: isHighlighted || false,
style: {
stroke: isHighlighted ? '#3b82f6' : (isInSubtree ? '#94a3b8' : '#e2e8f0'),
strokeWidth: isHighlighted ? 3 : 1.5,
opacity: selectedNode ? (isInSubtree ? 1 : 0.15) : 1,
},
labelStyle: { fontSize: 9, fill: '#64748b' },
labelBgStyle: { fill: '#f8fafc' },
markerEnd: { type: MarkerType.ArrowClosed, color: isHighlighted ? '#3b82f6' : '#94a3b8', width: 15, height: 15 },
}
})
}, [selectedNode, connectedNodes])
const [nodes, setNodes, onNodesChange] = useNodesState([])
const [edges, setEdges, onEdgesChange] = useEdgesState([])
// Update nodes/edges when dependencies change
useEffect(() => {
setNodes(initialNodes)
setEdges(initialEdges)
}, [initialNodes, initialEdges, setNodes, setEdges])
// Handle node click
const onNodeClick = useCallback((_event: React.MouseEvent, node: Node) => {
const screen = SCREENS.find(s => s.id === node.id)
if (selectedNode === node.id) {
if (screen?.url) {
window.open(`${baseUrl}${screen.url}`, '_blank')
}
return
}
setSelectedNode(node.id)
setSelectedCategory(null)
if (screen) {
setPreviewScreen(screen)
}
}, [selectedNode, baseUrl])
// Handle background click - deselect
const onPaneClick = useCallback(() => {
setSelectedNode(null)
setPreviewScreen(null)
}, [])
// Stats
const stats = {
totalScreens: SCREENS.length,
totalConnections: CONNECTIONS.length,
connectedCount: connectedNodes.size,
}
const categories = Object.keys(LABELS)
// Connected screens list
const connectedScreens = selectedNode
? SCREENS.filter(s => connectedNodes.has(s.id))
: []
return (
<div className="space-y-6">
{/* Header */}
<div className="bg-white rounded-xl border border-slate-200 p-6 shadow-sm">
<div className="flex items-center gap-4">
<div className="w-14 h-14 rounded-xl bg-violet-500 flex items-center justify-center text-2xl text-white">
🔀
</div>
<div>
<h2 className="text-xl font-bold text-slate-900">Compliance SDK Screen Flow</h2>
<p className="text-sm text-slate-500">
{stats.totalScreens} Screens mit {stats.totalConnections} Verbindungen
</p>
</div>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-4 gap-4">
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="text-3xl font-bold text-slate-800">{stats.totalScreens}</div>
<div className="text-sm text-slate-500">Screens</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="text-3xl font-bold text-violet-600">{stats.totalConnections}</div>
<div className="text-sm text-slate-500">Verbindungen</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm col-span-2">
{selectedNode ? (
<div className="flex items-center gap-3">
<div className="text-3xl">{previewScreen?.icon}</div>
<div>
<div className="font-bold text-slate-800">{previewScreen?.name}</div>
<div className="text-sm text-slate-500">
{stats.connectedCount} verbundene Screen{stats.connectedCount !== 1 ? 's' : ''}
</div>
</div>
<button
onClick={() => {
setSelectedNode(null)
setPreviewScreen(null)
}}
className="ml-auto px-3 py-1 text-sm bg-slate-100 hover:bg-slate-200 rounded-lg"
>
Zuruecksetzen
</button>
</div>
) : (
<div className="text-slate-500 text-sm">
Klicke auf einen Screen um den Subtree zu sehen
</div>
)}
</div>
</div>
{/* Category Filter */}
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="flex flex-wrap gap-2">
<button
onClick={() => {
setSelectedCategory(null)
setSelectedNode(null)
setPreviewScreen(null)
}}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
selectedCategory === null && !selectedNode
? 'bg-slate-800 text-white'
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
}`}
>
Alle ({SCREENS.length})
</button>
{categories.map((key) => {
const count = SCREENS.filter(s => s.category === key).length
const catColors = COLORS[key] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
return (
<button
key={key}
onClick={() => {
setSelectedCategory(selectedCategory === key ? null : key)
setSelectedNode(null)
setPreviewScreen(null)
}}
className="px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2"
style={{
background: selectedCategory === key ? catColors.border : catColors.bg,
color: selectedCategory === key ? 'white' : catColors.text,
}}
>
<span className="w-3 h-3 rounded-full" style={{ background: catColors.border }} />
{LABELS[key]} ({count})
</button>
)
})}
</div>
</div>
{/* Connected Screens List */}
{selectedNode && connectedScreens.length > 1 && (
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="text-sm font-medium text-slate-700 mb-3">Verbundene Screens:</div>
<div className="flex flex-wrap gap-2">
{connectedScreens.map((screen) => {
const catColors = COLORS[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
const isCurrentNode = screen.id === selectedNode
return (
<button
key={screen.id}
onClick={() => {
if (screen.url) {
window.open(`${baseUrl}${screen.url}`, '_blank')
}
}}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 ${
isCurrentNode ? 'ring-2 ring-violet-500' : ''
}`}
style={{
background: isCurrentNode ? catColors.border : catColors.bg,
color: isCurrentNode ? 'white' : catColors.text,
}}
>
<span>{screen.icon}</span>
{screen.name}
</button>
)
})}
</div>
</div>
)}
{/* Flow Diagram */}
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden" style={{ height: '500px' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick}
onPaneClick={onPaneClick}
fitView
fitViewOptions={{ padding: 0.2 }}
attributionPosition="bottom-left"
>
<Controls />
<MiniMap
nodeColor={(node) => {
const screen = SCREENS.find(s => s.id === node.id)
const catColors = screen ? COLORS[screen.category] : null
return catColors?.border || '#94a3b8'
}}
maskColor="rgba(0, 0, 0, 0.1)"
/>
<Background variant={BackgroundVariant.Dots} gap={12} size={1} />
<Panel position="top-left" className="bg-white/95 p-3 rounded-lg shadow-lg text-xs">
<div className="font-medium text-slate-700 mb-2">
🛡 Compliance SDK
</div>
<div className="space-y-1">
{categories.slice(0, 5).map((key) => {
const catColors = COLORS[key] || { bg: '#f1f5f9', border: '#94a3b8' }
return (
<div key={key} className="flex items-center gap-2">
<span
className="w-3 h-3 rounded"
style={{ background: catColors.bg, border: `1px solid ${catColors.border}` }}
/>
<span className="text-slate-600">{LABELS[key]}</span>
</div>
)
})}
</div>
<div className="mt-2 pt-2 border-t text-slate-400">
Klick = Subtree<br/>
Doppelklick = Oeffnen
</div>
</Panel>
</ReactFlow>
</div>
{/* Screen List */}
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
<div className="px-4 py-3 bg-slate-50 border-b flex items-center justify-between">
<h3 className="font-medium text-slate-700">
Alle Screens ({SCREENS.length})
</h3>
<span className="text-xs text-slate-400">{baseUrl}</span>
</div>
<div className="divide-y max-h-80 overflow-y-auto">
{SCREENS
.filter(s => !selectedCategory || s.category === selectedCategory)
.map((screen) => {
const catColors = COLORS[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
return (
<button
key={screen.id}
onClick={() => {
setSelectedNode(screen.id)
setSelectedCategory(null)
setPreviewScreen(screen)
}}
className="w-full flex items-center gap-4 p-3 hover:bg-slate-50 transition-colors text-left"
>
<span
className="w-9 h-9 rounded-lg flex items-center justify-center text-lg"
style={{ background: catColors.bg }}
>
{screen.icon}
</span>
<div className="flex-1 min-w-0">
<div className="font-medium text-slate-800 text-sm">{screen.name}</div>
<div className="text-xs text-slate-500 truncate">{screen.description}</div>
</div>
<span
className="px-2 py-1 rounded text-xs font-medium shrink-0"
style={{ background: catColors.bg, color: catColors.text }}
>
{LABELS[screen.category]}
</span>
</button>
)
})}
</div>
</div>
</div>
)
}