import { useMemo } from "react"; import { Loader2 } from "lucide-react"; import { groupPhasesByDependencyLevel, type DependencyEdge, } from "@codewalk-district/shared"; import { StatusBadge } from "@/components/StatusBadge"; import { mapEntityStatus, type StatusVariant } from "@/components/StatusDot"; import { cn } from "@/lib/utils"; import type { PhaseData } from "./ExecutionContext"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- interface PhaseGraphProps { phases: PhaseData[]; dependencyEdges: DependencyEdge[]; taskCountsByPhase: Record; activePhaseId: string | null; onSelectPhase: (id: string) => void; detailAgentByPhase: Map; allDisplayIndices: Map; } // --------------------------------------------------------------------------- // Styling maps // --------------------------------------------------------------------------- const railDotClasses: Record = { active: "bg-status-active-dot", success: "bg-status-success-dot", warning: "bg-status-warning-dot", error: "bg-status-error-dot", neutral: "bg-muted-foreground/30", urgent: "bg-status-urgent-dot", }; const selectedRingClasses: Record = { active: "ring-status-active-dot/40", success: "ring-status-success-dot/40", warning: "ring-status-warning-dot/40", error: "ring-status-error-dot/40", neutral: "ring-border", urgent: "ring-status-urgent-dot/40", }; // --------------------------------------------------------------------------- // PhaseGraph — vertical execution graph ordered by dependency depth // --------------------------------------------------------------------------- export function PhaseGraph({ phases, dependencyEdges, taskCountsByPhase, activePhaseId, onSelectPhase, detailAgentByPhase, allDisplayIndices, }: PhaseGraphProps) { const columns = useMemo( () => groupPhasesByDependencyLevel(phases, dependencyEdges), [phases, dependencyEdges], ); if (columns.length === 0) return null; return (
{columns.map((col, colIdx) => { const isParallel = col.phases.length > 1; const isFirst = colIdx === 0; // Connector coloring: green if all prior layers completed const prevAllCompleted = colIdx > 0 && columns .slice(0, colIdx) .every((c) => c.phases.every( (p) => mapEntityStatus(p.status) === "success", ), ); return (
{/* Connector line between layers */} {!isFirst && ( )} {isParallel ? (
Parallel · {col.phases.length}
{col.phases.map((phase) => ( onSelectPhase(phase.id)} detailAgent={ detailAgentByPhase.get(phase.id) ?? null } displayIndex={ allDisplayIndices.get(phase.id) ?? 0 } /> ))}
) : ( onSelectPhase(col.phases[0].id)} detailAgent={ detailAgentByPhase.get(col.phases[0].id) ?? null } displayIndex={ allDisplayIndices.get(col.phases[0].id) ?? 0 } /> )}
); })}
); } // --------------------------------------------------------------------------- // GraphNode — single phase in the execution graph // --------------------------------------------------------------------------- function GraphNode({ phase, taskCount, isSelected, onClick, detailAgent, displayIndex, }: { phase: PhaseData; taskCount: { complete: number; total: number }; isSelected: boolean; onClick: () => void; detailAgent: { status: string } | null; displayIndex: number; }) { const variant = mapEntityStatus(phase.status); const isDetailing = detailAgent?.status === "running" || detailAgent?.status === "waiting_for_input"; const detailDone = detailAgent?.status === "idle"; return ( ); } // --------------------------------------------------------------------------- // LayerConnector — vertical line between dependency layers // --------------------------------------------------------------------------- function LayerConnector({ completed }: { completed: boolean }) { return (
); }