diff --git a/packages/web/src/components/TaskDetailModal.tsx b/packages/web/src/components/TaskDetailModal.tsx new file mode 100644 index 0000000..ceb49ee --- /dev/null +++ b/packages/web/src/components/TaskDetailModal.tsx @@ -0,0 +1,170 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { StatusBadge } from "@/components/StatusBadge"; + +/** Serialized Task shape as returned by tRPC (Date serialized to string over JSON) */ +export interface SerializedTask { + id: string; + planId: string; + name: string; + description: string | null; + type: string; + priority: string; + status: string; + order: number; + createdAt: string; + updatedAt: string; +} + +interface DependencyInfo { + name: string; + status: string; +} + +interface TaskDetailModalProps { + task: SerializedTask | null; + phaseName: string; + agentName: string | null; + dependencies: DependencyInfo[]; + dependents: DependencyInfo[]; + onClose: () => void; + onQueueTask: (taskId: string) => void; + onStopTask: (taskId: string) => void; +} + +export function TaskDetailModal({ + task, + phaseName, + agentName, + dependencies, + dependents, + onClose, + onQueueTask, + onStopTask, +}: TaskDetailModalProps) { + const allDependenciesComplete = + dependencies.length === 0 || + dependencies.every((d) => d.status === "completed"); + + const canQueue = task !== null && task.status === "pending" && allDependenciesComplete; + const canStop = task !== null && task.status === "in_progress"; + + return ( + { if (!open) onClose(); }}> + + + {task?.name ?? "Task"} + Task details and dependencies + + + {task && ( +
+ {/* Metadata grid */} +
+
+ Status +
+ +
+
+
+ Priority +

{task.priority}

+
+
+ Phase +

{phaseName}

+
+
+ Type +

{task.type}

+
+
+ Agent +

+ {agentName ?? "Unassigned"} +

+
+
+ + {/* Description */} +
+

Description

+

+ {task.description ?? "No description"} +

+
+ + {/* Dependencies */} +
+

Dependencies

+ {dependencies.length === 0 ? ( +

+ No dependencies +

+ ) : ( +
    + {dependencies.map((dep) => ( +
  • + {dep.name} + +
  • + ))} +
+ )} +
+ + {/* Dependents (Blocks) */} +
+

Blocks

+ {dependents.length === 0 ? ( +

None

+ ) : ( +
    + {dependents.map((dep) => ( +
  • + {dep.name} + +
  • + ))} +
+ )} +
+
+ )} + + + + + +
+
+ ); +}