From 92d4d36421e629d4f0f8d2d25dcf37ac8343cea8 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Wed, 4 Feb 2026 21:33:20 +0100 Subject: [PATCH] feat(18-02): create PhaseAccordion component Expandable/collapsible phase container with chevron toggle, phase number + name header, task count (completed/total), StatusBadge, phase-level DependencyIndicator, and TaskRow list when expanded. --- .../web/src/components/PhaseAccordion.tsx | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 packages/web/src/components/PhaseAccordion.tsx diff --git a/packages/web/src/components/PhaseAccordion.tsx b/packages/web/src/components/PhaseAccordion.tsx new file mode 100644 index 0000000..4fa6020 --- /dev/null +++ b/packages/web/src/components/PhaseAccordion.tsx @@ -0,0 +1,102 @@ +import { useState } from "react"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import { StatusBadge } from "@/components/StatusBadge"; +import { DependencyIndicator } from "@/components/DependencyIndicator"; +import { TaskRow, type SerializedTask } from "@/components/TaskRow"; + +/** Phase shape as returned by tRPC (Date fields serialized to string over JSON) */ +interface SerializedPhase { + id: string; + initiativeId: string; + number: number; + name: string; + description: string | null; + status: string; + createdAt: string; + updatedAt: string; +} + +/** Task entry with associated metadata, pre-assembled by the parent page */ +interface TaskEntry { + task: SerializedTask; + agentName: string | null; + blockedBy: Array<{ name: string; status: string }>; +} + +interface PhaseAccordionProps { + phase: SerializedPhase; + tasks: TaskEntry[]; + defaultExpanded: boolean; + phaseDependencies: Array<{ name: string; status: string }>; + onTaskClick: (taskId: string) => void; +} + +export function PhaseAccordion({ + phase, + tasks, + defaultExpanded, + phaseDependencies, + onTaskClick, +}: PhaseAccordionProps) { + const [expanded, setExpanded] = useState(defaultExpanded); + + const completedCount = tasks.filter( + (t) => t.task.status === "completed", + ).length; + const totalCount = tasks.length; + + return ( +
+ {/* Phase header — clickable to toggle */} + + + {/* Phase-level dependency indicator (when blocked by another phase) */} + {phaseDependencies.length > 0 && ( + + )} + + {/* Expanded task list */} + {expanded && ( +
+ {tasks.map((entry, idx) => ( + onTaskClick(entry.task.id)} + /> + ))} +
+ )} +
+ ); +}