diff options
Diffstat (limited to 'makima/frontend/src/components/mesh/TaskList.tsx')
| -rw-r--r-- | makima/frontend/src/components/mesh/TaskList.tsx | 215 |
1 files changed, 161 insertions, 54 deletions
diff --git a/makima/frontend/src/components/mesh/TaskList.tsx b/makima/frontend/src/components/mesh/TaskList.tsx index a37e564..d013782 100644 --- a/makima/frontend/src/components/mesh/TaskList.tsx +++ b/makima/frontend/src/components/mesh/TaskList.tsx @@ -1,4 +1,5 @@ -import type { TaskSummary, TaskStatus } from "../../lib/api"; +import { useMemo } from "react"; +import type { TaskSummary, TaskStatus, ContractPhase } from "../../lib/api"; interface TaskListProps { tasks: TaskSummary[]; @@ -8,6 +9,13 @@ interface TaskListProps { onCreate: () => void; } +interface GroupedTasks { + contractId: string | null; + contractName: string | null; + contractPhase: ContractPhase | null; + tasks: TaskSummary[]; +} + function formatDate(dateStr: string): string { const date = new Date(dateStr); return date.toLocaleDateString("en-US", { @@ -61,6 +69,17 @@ function getStatusBgColor(status: TaskStatus): string { } } +function getPhaseColor(phase: ContractPhase | null): string { + switch (phase) { + case "research": return "text-blue-400"; + case "specify": return "text-cyan-400"; + case "plan": return "text-yellow-400"; + case "execute": return "text-green-400"; + case "review": return "text-purple-400"; + default: return "text-[#8b949e]"; + } +} + export function TaskList({ tasks, loading, @@ -68,6 +87,53 @@ export function TaskList({ onDelete, onCreate, }: TaskListProps) { + // Group tasks by contract + const groupedTasks = useMemo(() => { + // Separate root tasks (no parent) from subtasks + const rootTasks = tasks.filter((t) => !t.parentTaskId); + + // Group by contractId + const groups = new Map<string | null, GroupedTasks>(); + + for (const task of rootTasks) { + const key = task.contractId; + if (!groups.has(key)) { + groups.set(key, { + contractId: task.contractId, + contractName: task.contractName, + contractPhase: task.contractPhase, + tasks: [], + }); + } + groups.get(key)!.tasks.push(task); + } + + // Sort tasks within each group: supervisors first, then by status (running first), then by date + for (const group of groups.values()) { + group.tasks.sort((a, b) => { + // Supervisors always first + if (a.isSupervisor && !b.isSupervisor) return -1; + if (!a.isSupervisor && b.isSupervisor) return 1; + // Running tasks next + if (a.status === "running" && b.status !== "running") return -1; + if (a.status !== "running" && b.status === "running") return 1; + // Then by date (newest first) + return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + }); + } + + // Sort: contracts first (alphabetically by name), then orphan tasks + const sorted = Array.from(groups.values()).sort((a, b) => { + // Orphan tasks (no contract) go last + if (!a.contractId && b.contractId) return 1; + if (a.contractId && !b.contractId) return -1; + // Sort by contract name + return (a.contractName || "").localeCompare(b.contractName || ""); + }); + + return sorted; + }, [tasks]); + if (loading) { return ( <div className="panel h-full flex items-center justify-center"> @@ -76,8 +142,7 @@ export function TaskList({ ); } - // Separate root tasks (no parent) from subtasks - const rootTasks = tasks.filter((t) => !t.parentTaskId); + const totalTasks = groupedTasks.reduce((sum, g) => sum + g.tasks.length, 0); return ( <div className="panel h-full flex flex-col"> @@ -94,65 +159,107 @@ export function TaskList({ </div> <div className="flex-1 overflow-y-auto"> - {rootTasks.length === 0 ? ( + {totalTasks === 0 ? ( <div className="text-center text-[#9bc3ff] text-sm font-mono opacity-60 py-8"> No tasks yet. Create one to start orchestrating Claude Code instances. </div> ) : ( - <div className="divide-y divide-[rgba(117,170,252,0.15)]"> - {rootTasks.map((task) => ( - <div - key={task.id} - className="p-4 hover:bg-[rgba(117,170,252,0.05)] transition-colors" - > - <div className="flex items-start justify-between gap-4"> - <button - onClick={() => onSelect(task.id)} - className="flex-1 text-left" - > - <div className="flex items-center gap-2 mb-1"> - <h3 className="font-mono text-sm text-[#dbe7ff]"> - {task.name} - </h3> - <span - className={`px-2 py-0.5 font-mono text-[10px] uppercase ${getStatusColor( - task.status - )} ${getStatusBgColor(task.status)} border border-current/20`} - > - {task.status} + <div> + {groupedTasks.map((group) => ( + <div key={group.contractId || "orphan"}> + {/* Contract header */} + <div className="sticky top-0 bg-[#0d1117] border-b border-[rgba(117,170,252,0.25)] px-4 py-2 flex items-center gap-2"> + {group.contractId ? ( + <> + <span className="font-mono text-xs text-[#dbe7ff] font-medium"> + {group.contractName} </span> - {task.depth === 0 && task.subtaskCount > 0 && ( - <span className="px-2 py-0.5 font-mono text-[10px] text-purple-400 bg-purple-400/10 border border-purple-400/20"> - Orchestrator + {group.contractPhase && ( + <span className={`font-mono text-[10px] ${getPhaseColor(group.contractPhase)}`}> + [{group.contractPhase}] </span> )} - {task.priority > 0 && ( - <span className="px-2 py-0.5 font-mono text-[10px] text-orange-400 bg-orange-400/10 border border-orange-400/20"> - P{task.priority} - </span> - )} - </div> - {task.progressSummary && ( - <p className="font-mono text-xs text-[#9bc3ff] mb-2 line-clamp-2"> - {task.progressSummary} - </p> - )} - <div className="flex gap-4 font-mono text-[10px] text-[#75aafc]"> - {task.subtaskCount > 0 && ( - <span>{task.subtaskCount} subtasks</span> - )} - <span>{formatDate(task.createdAt)}</span> + <span className="font-mono text-[10px] text-[#8b949e]"> + ({group.tasks.length}) + </span> + </> + ) : ( + <span className="font-mono text-xs text-[#8b949e] italic"> + Unassigned Tasks ({group.tasks.length}) + </span> + )} + </div> + + {/* Tasks in this group */} + <div className="divide-y divide-[rgba(117,170,252,0.15)]"> + {group.tasks.map((task) => ( + <div + key={task.id} + className={`p-4 hover:bg-[rgba(117,170,252,0.05)] transition-colors ${ + task.isSupervisor + ? "bg-[rgba(117,170,252,0.08)] border-l-2 border-[#75aafc]" + : "" + }`} + > + <div className="flex items-start justify-between gap-4"> + <button + onClick={() => onSelect(task.id)} + className="flex-1 text-left" + > + <div className="flex items-center gap-2 mb-1"> + <h3 className="font-mono text-sm text-[#dbe7ff]"> + {task.name} + </h3> + <span + className={`px-2 py-0.5 font-mono text-[10px] uppercase ${getStatusColor( + task.status + )} ${getStatusBgColor(task.status)} border border-current/20`} + > + {task.status} + </span> + {task.isSupervisor && ( + <span className="px-2 py-0.5 font-mono text-[10px] text-[#75aafc] bg-[#0f3c78] border border-[#3f6fb3] uppercase"> + Supervisor + </span> + )} + {!task.isSupervisor && task.depth === 0 && task.subtaskCount > 0 && ( + <span className="px-2 py-0.5 font-mono text-[10px] text-purple-400 bg-purple-400/10 border border-purple-400/20"> + Orchestrator + </span> + )} + {task.priority > 0 && ( + <span className="px-2 py-0.5 font-mono text-[10px] text-orange-400 bg-orange-400/10 border border-orange-400/20"> + P{task.priority} + </span> + )} + </div> + {task.progressSummary && ( + <p className="font-mono text-xs text-[#9bc3ff] mb-2 line-clamp-2"> + {task.progressSummary} + </p> + )} + <div className="flex gap-4 font-mono text-[10px] text-[#75aafc]"> + {task.subtaskCount > 0 && ( + <span>{task.subtaskCount} subtasks</span> + )} + <span>{formatDate(task.createdAt)}</span> + </div> + </button> + {/* Supervisor tasks cannot be deleted directly - they are deleted with the contract */} + {!task.isSupervisor && ( + <button + onClick={(e) => { + e.stopPropagation(); + onDelete(task.id); + }} + className="px-2 py-1 font-mono text-[10px] text-red-400 hover:bg-red-400/10 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase" + > + Delete + </button> + )} + </div> </div> - </button> - <button - onClick={(e) => { - e.stopPropagation(); - onDelete(task.id); - }} - className="px-2 py-1 font-mono text-[10px] text-red-400 hover:bg-red-400/10 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase" - > - Delete - </button> + ))} </div> </div> ))} |
