import { useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { listInitiativeRoadmapDependencies } from '../api/roadmap.js' import { ROADMAP_ITEM_TYPE_LABELS } from '../constants/status.js' import { gatePath } from '../utils/routes.js' import { computeGateGraphLayout } from '../plan/gateGraphLayout.js' import { LoadingState } from './LoadingState.jsx' import { ErrorState } from './ErrorState.jsx' import { EmptyState } from './EmptyState.jsx' function edgePath(edge) { const midY = (edge.y1 + edge.y2) / 2 return `M ${edge.x1} ${edge.y1} C ${edge.x1} ${midY}, ${edge.x2} ${midY}, ${edge.x2} ${edge.y2}` } function truncateTitle(title, max = 28) { if (!title || title.length <= max) return title || 'Gate' return `${title.slice(0, max - 1)}…` } export function GateMapView({ initiativeId, items }) { const navigate = useNavigate() const [dependencies, setDependencies] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { if (!initiativeId) { setDependencies([]) setLoading(false) return undefined } let cancelled = false setLoading(true) setError(null) listInitiativeRoadmapDependencies(initiativeId) .then((data) => { if (!cancelled) setDependencies(Array.isArray(data) ? data : []) }) .catch((err) => { if (!cancelled) setError(err.message || 'Abhängigkeiten konnten nicht geladen werden.') }) .finally(() => { if (!cancelled) setLoading(false) }) return () => { cancelled = true } }, [initiativeId]) const layout = useMemo( () => computeGateGraphLayout(items, dependencies), [items, dependencies], ) if (!items.length) { return ( ) } if (loading) { return } if (error) { return } return ( Zielzustände (Graph) Read-only Übersicht der Gate-Abhängigkeiten — Kanten auf der Gate-Detailseite pflegen, Verify und Kriterien dort ebenfalls. Voraussetzung Blockiert Bezug {layout.edges.map((edge) => ( ))} {layout.nodes.map((node) => { const item = node.item return ( navigate(gatePath(node.id))} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() navigate(gatePath(node.id)) } }} > {truncateTitle(item.title)} {ROADMAP_ITEM_TYPE_LABELS[item.item_type] || item.item_type || 'Gate'} ) })} {dependencies.length === 0 && ( Noch keine Kanten — auf der Gate-Detailseite unter „Abhängigkeiten“ anlegen; ohne Kanten gelten Gates nach Reihenfolge. )} ) }
Read-only Übersicht der Gate-Abhängigkeiten — Kanten auf der Gate-Detailseite pflegen, Verify und Kriterien dort ebenfalls.
Noch keine Kanten — auf der Gate-Detailseite unter „Abhängigkeiten“ anlegen; ohne Kanten gelten Gates nach Reihenfolge.