'use client' /** * Screen Flow - State management hook */ import { useCallback, useState, useMemo, useEffect } from 'react' import { Node, Edge, useNodesState, useEdgesState, MarkerType } from 'reactflow' import type { ScreenDefinition, FlowType } from './types' import { STUDIO_SCREENS, ADMIN_SCREENS, STUDIO_CONNECTIONS, ADMIN_CONNECTIONS, STUDIO_COLORS, ADMIN_COLORS, STUDIO_LABELS, ADMIN_LABELS, } from './data' import { findConnectedNodes, getNodePosition } from './helpers' export function useScreenFlow() { const [flowType, setFlowType] = useState('admin') const [selectedCategory, setSelectedCategory] = useState(null) const [selectedNode, setSelectedNode] = useState(null) const [previewScreen, setPreviewScreen] = useState(null) // Derived data based on flow type 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:3002' // BFS connected nodes const connectedNodes = useMemo(() => { if (!selectedNode) return new Set() return findConnectedNodes(selectedNode, connections, 'children') }, [selectedNode, connections]) // Build ReactFlow nodes 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]) // Build ReactFlow edges 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([]) // Sync memoized nodes/edges into ReactFlow state useEffect(() => { setNodes(initialNodes) setEdges(initialEdges) }, [initialNodes, initialEdges, setNodes, setEdges]) // Reset on flow type change const handleFlowTypeChange = useCallback((newType: FlowType) => { setFlowType(newType) setSelectedNode(null) setSelectedCategory(null) setPreviewScreen(null) }, []) // Node click: select or open on double-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) } }, [screens, baseUrl, selectedNode]) // Background click: deselect const onPaneClick = useCallback(() => { setSelectedNode(null) setPreviewScreen(null) }, []) const stats = { totalScreens: screens.length, totalConnections: connections.length, connectedCount: connectedNodes.size, } const categories = Object.keys(labels) const connectedScreens = selectedNode ? screens.filter(s => connectedNodes.has(s.id)) : [] return { flowType, selectedCategory, setSelectedCategory, selectedNode, setSelectedNode, previewScreen, setPreviewScreen, screens, colors, labels, baseUrl, connectedNodes, nodes, edges, onNodesChange, onEdgesChange, handleFlowTypeChange, onNodeClick, onPaneClick, stats, categories, connectedScreens, } }