'use client' /** * Screen Flow Visualization * * Visualisiert alle Screens aus: * - Studio (Port 8000): Lehrer-Oberflaeche * - Admin (Port 3000): Admin Panel */ 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' import AdminLayout from '@/components/admin/AdminLayout' import { ScreenDefinition, FlowType } from './_components/types' import { STUDIO_SCREENS, STUDIO_CONNECTIONS, ADMIN_SCREENS, ADMIN_CONNECTIONS, STUDIO_COLORS, ADMIN_COLORS, STUDIO_LABELS, ADMIN_LABELS, } from './_components/screen-data' import { findConnectedNodes, constructEmbedUrl, getNodePosition } from './_components/flow-helpers' export default function ScreenFlowPage() { const [flowType, setFlowType] = useState('studio') const [selectedCategory, setSelectedCategory] = useState(null) const [selectedNode, setSelectedNode] = useState(null) const [previewUrl, setPreviewUrl] = useState(null) const [previewScreen, setPreviewScreen] = useState(null) const screens = flowType === 'studio' ? STUDIO_SCREENS : ADMIN_SCREENS const connections = flowType === 'studio' ? STUDIO_CONNECTIONS : ADMIN_CONNECTIONS const colors = flowType === 'studio' ? STUDIO_COLORS : ADMIN_COLORS const labels = flowType === 'studio' ? STUDIO_LABELS : ADMIN_LABELS const baseUrl = flowType === 'studio' ? 'http://macmini:8000' : 'http://macmini:3000' const connectedNodes = useMemo(() => { if (!selectedNode) return new Set() return findConnectedNodes(selectedNode, connections, 'children') }, [selectedNode, connections]) 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, screens, flowType) 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: (
{screen.icon}
{screen.name}
) }, 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' }, } }) }, [screens, colors, flowType, selectedCategory, selectedNode, connectedNodes]) 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 }, } }) }, [connections, selectedNode, connectedNodes]) const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) useEffect(() => { setNodes(initialNodes); setEdges(initialEdges) }, [initialNodes, initialEdges]) const handleFlowTypeChange = useCallback((newType: FlowType) => { setFlowType(newType); setSelectedNode(null); setSelectedCategory(null); setPreviewUrl(null); setPreviewScreen(null) }, []) 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?.url) { setPreviewUrl(constructEmbedUrl(baseUrl, screen.url)); setPreviewScreen(screen) } }, [screens, baseUrl, selectedNode]) const onPaneClick = useCallback(() => { setSelectedNode(null); setPreviewUrl(null); setPreviewScreen(null) }, []) const closePreview = useCallback(() => { setPreviewUrl(null); setPreviewScreen(null); setSelectedNode(null) }, []) const categories = Object.keys(labels) const connectedScreens = selectedNode ? screens.filter(s => connectedNodes.has(s.id)) : [] return ( {/* Flow Type Selector */}
{(['studio', 'admin'] as const).map(type => { const isActive = flowType === type const cfg = type === 'studio' ? { icon: '🎓', label: 'Studio (Port 8000)', sub: 'Lehrer-Oberflaeche', count: STUDIO_SCREENS.length, activeColor: 'border-green-500 bg-green-50', iconBg: 'bg-green-500 text-white' } : { icon: '⚙️', label: 'Admin (Port 3000)', sub: 'Admin Panel', count: ADMIN_SCREENS.length, activeColor: 'border-purple-500 bg-purple-50', iconBg: 'bg-purple-500 text-white' } return ( ) })}
{/* Stats */}
{screens.length}
Screens
{connections.length}
Verbindungen
{selectedNode ? (
{previewScreen?.icon}
{previewScreen?.name}
{connectedNodes.size} verbundene Screen{connectedNodes.size !== 1 ? 's' : ''}
) : (
Klicke auf einen Screen um den Subtree und die Vorschau zu sehen
)}
{/* Category Filter */}
{categories.map((key) => { const count = screens.filter(s => s.category === key).length const catColors = colors[key] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' } return ( ) })}
{/* Connected Screens List */} {selectedNode && connectedScreens.length > 1 && (
Verbundene Screens:
{connectedScreens.map((screen) => { const catColors = colors[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' } const isCurrentNode = screen.id === selectedNode return ( ) })}
)} {/* Flow Diagram */}
{ const s = screens.find(s => s.id === node.id); return s ? (colors[s.category]?.border || '#94a3b8') : '#94a3b8' }} maskColor="rgba(0, 0, 0, 0.1)" />
{flowType === 'studio' ? '🎓 Studio' : '⚙️ Admin'}
{categories.slice(0, 4).map((key) => { const catColors = colors[key] || { bg: '#f1f5f9', border: '#94a3b8' }; return (
{labels[key]}
) })}
Klick = Subtree + Preview
Doppelklick = Oeffnen
{/* Iframe Preview */} {previewUrl && (
{previewScreen?.icon}

{previewScreen?.name}

{previewScreen?.description}

{previewScreen?.url} Oeffnen