import { useNavigate } from "react-router"; import type { ChainStep, ContractPhase } from "../../lib/api"; import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar"; interface StepDiagramProps { steps: ChainStep[]; } const statusBorderColors: Record = { pending: "border-[#555]", running: "border-yellow-400", passed: "border-green-400", failed: "border-red-400", }; const statusDotColors: Record = { pending: "bg-[#555]", running: "bg-yellow-400", passed: "bg-green-400", failed: "bg-red-400", }; /** * Assign depth to each step via topological sort. * Steps with no dependsOn = depth 0. Steps depending only on depth-0 = depth 1. Etc. */ function assignDepths(steps: ChainStep[]): Map { const depths = new Map(); const stepMap = new Map(steps.map((s) => [s.id, s])); function getDepth(id: string): number { if (depths.has(id)) return depths.get(id)!; const step = stepMap.get(id); if (!step || !step.dependsOn || step.dependsOn.length === 0) { depths.set(id, 0); return 0; } const maxParent = Math.max( ...step.dependsOn.map((depId) => getDepth(depId)) ); const d = maxParent + 1; depths.set(id, d); return d; } for (const step of steps) { getDepth(step.id); } return depths; } export function StepDiagram({ steps }: StepDiagramProps) { const navigate = useNavigate(); if (steps.length === 0) { return (

No steps to display.

); } const depths = assignDepths(steps); const maxDepth = Math.max(...Array.from(depths.values())); // Group steps by depth const levels: ChainStep[][] = []; for (let d = 0; d <= maxDepth; d++) { levels.push( steps .filter((s) => depths.get(s.id) === d) .sort((a, b) => a.orderIndex - b.orderIndex) ); } // Build position map for connectors const stepPositions = new Map(); levels.forEach((level, li) => { level.forEach((step, si) => { stepPositions.set(step.id, { level: li, index: si }); }); }); return (
{levels.map((level, li) => (
{li > 0 && (
)} {level.map((step) => { const borderColor = statusBorderColors[step.status] || "border-[#555]"; const dotColor = statusDotColors[step.status] || "bg-[#555]"; const summary = step.contractSummary; const hasContract = !!step.contractId; return (
{ if (hasContract) navigate(`/contracts/${step.contractId}`); }} title={hasContract ? "View contract" : undefined} >
{step.name} {hasContract && ( )}
{summary && ( <>
{summary.tasksDone}/{summary.taskCount} tasks {summary.tasksRunning > 0 && ( {summary.tasksRunning} running )} {summary.tasksFailed > 0 && ( {summary.tasksFailed} failed )}
)}
); })}
))}
); }