import { useState, useCallback } from "react"; import type { TaskSummary, TaskStatus } from "../../lib/api"; interface TaskTreeProps { tasks: TaskSummary[]; supervisorTaskId: string | null; onSelect: (taskId: string) => void; onStartSupervisor?: () => void; loading?: boolean; fetchSubtasks?: (taskId: string) => Promise; } interface TreeNodeProps { task: TaskSummary; isSupervisorTask: boolean; onSelect: (taskId: string) => void; depth: number; fetchSubtasks?: (taskId: string) => Promise; } function getStatusColor(status: TaskStatus): string { switch (status) { case "pending": return "text-[#9bc3ff]"; case "running": return "text-green-400"; case "paused": return "text-yellow-400"; case "blocked": return "text-orange-400"; case "done": return "text-emerald-400"; case "failed": return "text-red-400"; case "merged": return "text-purple-400"; default: return "text-[#9bc3ff]"; } } function getStatusIcon(status: TaskStatus): string { switch (status) { case "pending": return "○"; case "running": return "◉"; case "paused": return "◎"; case "blocked": return "◈"; case "done": return "●"; case "failed": return "✕"; case "merged": return "◆"; default: return "○"; } } function TreeNode({ task, isSupervisorTask, onSelect, depth, fetchSubtasks }: TreeNodeProps) { const [expanded, setExpanded] = useState(isSupervisorTask); // Supervisor expanded by default const [children, setChildren] = useState(null); const [loadingChildren, setLoadingChildren] = useState(false); const hasSubtasks = task.subtaskCount > 0; const handleToggle = useCallback(async () => { if (!hasSubtasks) return; if (expanded) { setExpanded(false); } else { if (!children && fetchSubtasks) { setLoadingChildren(true); try { const subtasks = await fetchSubtasks(task.id); setChildren(subtasks); } catch (err) { console.error("Failed to fetch subtasks:", err); } finally { setLoadingChildren(false); } } setExpanded(true); } }, [expanded, children, hasSubtasks, task.id, fetchSubtasks]); const indent = depth * 16; return (
{/* Expand/Collapse button */} {/* Supervisor badge or status icon */} {isSupervisorTask ? ( Supervisor ) : ( {getStatusIcon(task.status)} )} {/* Task name - clickable */} {/* Status for supervisor */} {isSupervisorTask && ( {task.status} )} {/* Subtask count badge */} {hasSubtasks && !isSupervisorTask && ( {task.subtaskCount} sub )} {/* Priority indicator */} {task.priority > 0 && ( P{task.priority} )}
{/* Children */} {expanded && children && children.length > 0 && (
{children.map((child) => ( ))}
)}
); } // Stats summary component export interface TaskTreeStats { total: number; pending: number; running: number; paused: number; blocked: number; done: number; failed: number; merged: number; } export function calculateTreeStats(tasks: TaskSummary[]): TaskTreeStats { const stats: TaskTreeStats = { total: tasks.length, pending: 0, running: 0, paused: 0, blocked: 0, done: 0, failed: 0, merged: 0, }; for (const task of tasks) { // Skip supervisor task in stats if (task.isSupervisor) continue; switch (task.status) { case "pending": stats.pending++; break; case "running": stats.running++; break; case "paused": stats.paused++; break; case "blocked": stats.blocked++; break; case "done": stats.done++; break; case "failed": stats.failed++; break; case "merged": stats.merged++; break; } } // Adjust total to exclude supervisor stats.total = tasks.filter(t => !t.isSupervisor).length; return stats; } // Progress bar for task tree export function TaskTreeProgressBar({ stats }: { stats: TaskTreeStats }) { if (stats.total === 0) return null; const completedPercent = ((stats.done + stats.merged) / stats.total) * 100; const runningPercent = (stats.running / stats.total) * 100; const failedPercent = (stats.failed / stats.total) * 100; return (
{/* Progress bar */}
{/* Summary */}
{stats.done + stats.merged} / {stats.total} completed {stats.running > 0 && ( {stats.running} running )} {stats.failed > 0 && ( {stats.failed} failed )}
); } export function TaskTree({ tasks, supervisorTaskId, onSelect, onStartSupervisor, loading = false, fetchSubtasks, }: TaskTreeProps) { if (loading) { return (
Loading tasks...
); } // Separate supervisor from other tasks const supervisorTask = tasks.find(t => t.id === supervisorTaskId || t.isSupervisor); const workerTasks = tasks.filter(t => t.id !== supervisorTaskId && !t.isSupervisor && !t.parentTaskId); // Calculate stats for worker tasks const stats = calculateTreeStats(tasks); return (
{/* Supervisor Section */}

Contract Supervisor

{supervisorTask && supervisorTask.status === "pending" && onStartSupervisor && ( )}
{supervisorTask ? ( ) : (

No supervisor task found

)}
{/* Progress Section */} {stats.total > 0 && (

Task Progress

)} {/* Worker Tasks Section */} {workerTasks.length > 0 && (

Worker Tasks ({workerTasks.length})

{workerTasks.map((task) => ( ))}
)} {/* Empty State */} {workerTasks.length === 0 && !supervisorTask && (
No tasks in this contract
)}
); }