import { useCallback, useEffect, useState } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import { IconArrowLeft, IconColumnInsertRight, IconTicket, IconAlertTriangle } from '@tabler/icons-react' import { PageHeader, Card, Button, Badge } from '../components/ui' import RoadmapBoard from '../components/roadmap/RoadmapBoard' import NodeDetailPanel from '../components/roadmap/NodeDetailPanel' import RoadmapProgressBar from '../components/roadmap/RoadmapProgressBar' import { useAuth } from '../context/AuthContext' import { useRoadmaps } from '../hooks/useRoadmaps' import { useRoadmapNodes } from '../hooks/useRoadmapNodes' import { databases, DATABASE_ID, COLLECTIONS } from '../lib/appwrite' import { computeProgress, NODE_KIND, ROADMAP_STATUS } from '../lib/roadmaps' export default function RoadmapDetailPage() { const { id } = useParams() const navigate = useNavigate() const { user } = useAuth() const { fetchById, updateRoadmap, completePlan } = useRoadmaps() const { nodes, fetchNodes, createNode, updateNode, deleteNode, connectNodes, disconnectNodes, setNodeStatus, } = useRoadmapNodes() const [roadmap, setRoadmap] = useState(null) const [ticketStatus, setTicketStatus] = useState(null) const [selectedNode, setSelectedNode] = useState(null) const [notFound, setNotFound] = useState(false) const load = useCallback(async () => { const doc = await fetchById(id) if (!doc) { setNotFound(true) return } setRoadmap(doc) await fetchNodes(doc.$id) if (doc.ticketId) { try { const ticket = await databases.getDocument(DATABASE_ID, COLLECTIONS.WORKORDERS, doc.ticketId) setTicketStatus(ticket.status) } catch { setTicketStatus(null) } } }, [id, fetchById, fetchNodes]) useEffect(() => { load() }, [load]) // Teamarbeit: bei Fenster-Fokus neu laden (einfacher Schutz gegen veraltete Ansicht) useEffect(() => { const onFocus = () => load() window.addEventListener('focus', onFocus) return () => window.removeEventListener('focus', onFocus) }, [load]) // Ausgewaehlten Knoten mit frischen Daten aus der Liste spiegeln useEffect(() => { if (!selectedNode) return const fresh = nodes.find((n) => n.$id === selectedNode.$id) if (!fresh) setSelectedNode(null) else if (fresh !== selectedNode) setSelectedNode(fresh) }, [nodes]) // eslint-disable-line react-hooks/exhaustive-deps if (notFound) { return (
) } if (!roadmap) { return

Lade Roadmap…

} const readOnly = roadmap.status === ROADMAP_STATUS.COMPLETED const progress = computeProgress(nodes) const ticketClosedButActive = !readOnly && ticketStatus === 'Closed' const handleAddNode = async (column, kind, rowCount) => { const result = await createNode(roadmap.$id, { title: kind === NODE_KIND.GOAL ? 'Neues Ziel' : 'Neuer Schritt', column, row: rowCount, kind, }) if (result.success) setSelectedNode(result.data) else window.alert(result.error) } const handleConnect = async (source, target) => { const result = await connectNodes(source, target) if (!result.success) window.alert(result.error) } const handleSaveNode = async (node, data) => updateNode(node.$id, data) const handleSetStatus = async (node, status) => { const result = await setNodeStatus(roadmap, node, status, user) if (!result.success) { window.alert(result.error) return } if (result.allGoalsDone) { const ok = window.confirm( 'Alle Ziel-Knoten sind fertig!\n\n' + 'Plan abschliessen und archivieren? Das Projektticket wird dabei geschlossen.' ) if (ok) { const done = await completePlan(roadmap, user) if (done.success) setRoadmap(done.data) else window.alert(`Abschliessen fehlgeschlagen: ${done.error}`) } } } const handleDeleteNode = async (node) => { if (!window.confirm(`Knoten "${node.title}" loeschen?`)) return const result = await deleteNode(node) if (!result.success) window.alert(result.error) else setSelectedNode(null) } const handleDisconnect = async (sourceId, target) => { const result = await disconnectNodes(sourceId, target) if (!result.success) window.alert(result.error) } /** Neue Phase vor den Zielen einfuegen: Ziel-Knoten ruecken eine Spalte nach rechts. */ const handleAddColumn = async () => { const newCount = (roadmap.columnCount || 3) + 1 if (newCount > 50) return const goals = nodes.filter((n) => n.kind === NODE_KIND.GOAL) for (const goal of goals) { await updateNode(goal.$id, { column: newCount - 1 }) } const result = await updateRoadmap(roadmap.$id, { columnCount: newCount }) if (result.success) setRoadmap(result.data) else window.alert(result.error) } return (
{readOnly && Abgeschlossen} {roadmap.woid && ( )} {!readOnly && ( )}
} /> {ticketClosedButActive && (
Das Projektticket wurde manuell geschlossen, der Plan ist aber erst zu {progress.percent}% fertig. Statusaenderungen werden weiterhin im Ticketverlauf dokumentiert.
)}
{selectedNode && ( setSelectedNode(null)} onSave={handleSaveNode} onSetStatus={handleSetStatus} onDelete={handleDeleteNode} onDisconnect={handleDisconnect} /> )}
) }