import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { IconRoute, IconAlertTriangle } from '@tabler/icons-react' import RoadmapProgressBar from './roadmap/RoadmapProgressBar' import { fetchRoadmapByTicketId, fetchRoadmapNodes, computeProgress, isNodeOverdue, NODE_KIND, NODE_STATUS, ROADMAP_STATUS, } from '../lib/roadmaps' /** * Roadmap-Tracking im Projektticket: Fortschritt, Mini-Uebersicht und Link zum Board. * Rendert nichts, wenn zum Ticket keine Roadmap existiert. */ export default function TicketRoadmapPanel({ ticket }) { const [roadmap, setRoadmap] = useState(null) const [nodes, setNodes] = useState([]) const [loaded, setLoaded] = useState(false) useEffect(() => { let cancelled = false async function load() { try { const doc = await fetchRoadmapByTicketId(ticket.$id) if (cancelled) return setRoadmap(doc) if (doc) setNodes(await fetchRoadmapNodes(doc.$id)) } catch { /* Panel ist optional */ } finally { if (!cancelled) setLoaded(true) } } load() return () => { cancelled = true } }, [ticket.$id]) if (!loaded || !roadmap) return null const progress = computeProgress(nodes) const relevant = nodes.filter((n) => n.kind !== NODE_KIND.START) const counts = { open: relevant.filter((n) => (n.status || NODE_STATUS.OPEN) === NODE_STATUS.OPEN).length, inProgress: relevant.filter((n) => n.status === NODE_STATUS.IN_PROGRESS).length, done: relevant.filter((n) => n.status === NODE_STATUS.DONE).length, } const nextDue = relevant .filter((n) => n.deadline && n.status !== NODE_STATUS.DONE) .sort((a, b) => a.deadline.localeCompare(b.deadline)) .slice(0, 3) const closedButActive = ticket.status === 'Closed' && roadmap.status === ROADMAP_STATUS.ACTIVE return (
Roadmap: {roadmap.title}
Zur Roadmap
Offen: {counts.open} In Arbeit: {counts.inProgress} Fertig: {counts.done} {roadmap.status === ROADMAP_STATUS.COMPLETED && Plan abgeschlossen}
{nextDue.length > 0 && (
Naechste Deadlines: {nextDue.map((n, i) => ( {i > 0 && ' ยท '}{n.title} ({n.deadline.split('-').reverse().join('.')}) ))}
)} {closedButActive && (
Ticket ist geschlossen, aber der Plan ist erst zu {progress.percent}% fertig.
)}
) }