import { useMemo, useState } from "react"; import type { TaskSummary, TaskStatus, ContractPhase, ContractStatus } from "../../lib/api"; interface TaskListProps { tasks: TaskSummary[]; loading: boolean; onSelect: (id: string) => void; onDelete: (id: string) => void; onCreate: () => void; } interface GroupedTasks { contractId: string | null; contractName: string | null; contractPhase: ContractPhase | null; contractStatus: ContractStatus | null; tasks: TaskSummary[]; } type StatusFilter = 'all' | 'active' | 'completed' | 'archived'; function formatDate(dateStr: string): string { const date = new Date(dateStr); return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "2-digit", minute: "2-digit", }); } 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 getStatusBgColor(status: TaskStatus): string { switch (status) { case "pending": return "bg-[rgba(117,170,252,0.1)]"; case "running": return "bg-green-400/10"; case "paused": return "bg-yellow-400/10"; case "blocked": return "bg-orange-400/10"; case "done": return "bg-emerald-400/10"; case "failed": return "bg-red-400/10"; case "merged": return "bg-purple-400/10"; default: return "bg-[rgba(117,170,252,0.1)]"; } } 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, onSelect, onDelete, onCreate, }: TaskListProps) { // Filter state - default to 'active' to show only active contracts const [statusFilter, setStatusFilter] = useState('active'); // Group tasks by contract and filter by status const groupedTasks = useMemo(() => { // Separate root tasks (no parent) from subtasks const rootTasks = tasks.filter((t) => !t.parentTaskId); // Filter tasks based on contract status const filteredTasks = statusFilter === 'all' ? rootTasks : rootTasks.filter((task) => { // Tasks without a contract go in 'all' only, or treat them as 'active' if (!task.contractId) { return statusFilter === 'active'; } return task.contractStatus === statusFilter; }); // Group by contractId const groups = new Map(); for (const task of filteredTasks) { const key = task.contractId; if (!groups.has(key)) { groups.set(key, { contractId: task.contractId, contractName: task.contractName, contractPhase: task.contractPhase, contractStatus: task.contractStatus, 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, statusFilter]); if (loading) { return (
Loading tasks...
); } const totalTasks = groupedTasks.reduce((sum, g) => sum + g.tasks.length, 0); const filterOptions: { value: StatusFilter; label: string }[] = [ { value: 'all', label: 'All' }, { value: 'active', label: 'Active' }, { value: 'completed', label: 'Completed' }, { value: 'archived', label: 'Archive' }, ]; return (
MESH//TASKS
{/* Status filter toggle */}
{filterOptions.map((option) => ( ))}
{totalTasks === 0 ? (
{statusFilter === 'all' ? 'No tasks yet. Create one to start orchestrating Claude Code instances.' : `No ${statusFilter} tasks found.`}
) : (
{groupedTasks.map((group) => (
{/* Contract header */}
{group.contractId ? ( <> {group.contractName} {group.contractPhase && ( [{group.contractPhase}] )} ({group.tasks.length}) ) : ( Unassigned Tasks ({group.tasks.length}) )}
{/* Tasks in this group */}
{group.tasks.map((task) => (
{/* Supervisor tasks cannot be deleted directly - they are deleted with the contract */} {!task.isSupervisor && ( )}
))}
))}
)}
); }