import { useMemo } from "react"; import type { ContractWithRelations, ContractPhase } from "../../lib/api"; // Phase deliverables configuration (mirrors backend phase_guidance.rs) interface RecommendedFile { templateId: string; name: string; priority: "required" | "recommended" | "optional"; description: string; } interface PhaseDeliverables { phase: ContractPhase; files: RecommendedFile[]; requiresRepository: boolean; requiresTasks: boolean; guidance: string; } const PHASE_DELIVERABLES: Record = { research: { phase: "research", files: [ { templateId: "research-notes", name: "Research Notes", priority: "recommended", description: "Document findings and insights" }, { templateId: "competitor-analysis", name: "Competitor Analysis", priority: "recommended", description: "Analyze competitors" }, { templateId: "user-research", name: "User Research", priority: "optional", description: "User interviews and personas" }, ], requiresRepository: false, requiresTasks: false, guidance: "Gather information and document findings before moving to Specify.", }, specify: { phase: "specify", files: [ { templateId: "requirements", name: "Requirements Document", priority: "required", description: "Functional and non-functional requirements" }, { templateId: "user-stories", name: "User Stories", priority: "recommended", description: "Features from user perspective" }, { templateId: "acceptance-criteria", name: "Acceptance Criteria", priority: "recommended", description: "Testable conditions for completion" }, ], requiresRepository: false, requiresTasks: false, guidance: "Define clear requirements and acceptance criteria.", }, plan: { phase: "plan", files: [ { templateId: "architecture", name: "Architecture Document", priority: "recommended", description: "System architecture and design" }, { templateId: "task-breakdown", name: "Task Breakdown", priority: "required", description: "Work broken into tasks" }, { templateId: "technical-design", name: "Technical Design", priority: "optional", description: "Detailed technical specs" }, ], requiresRepository: true, requiresTasks: false, guidance: "Design the solution and create a task breakdown. Configure a repository.", }, execute: { phase: "execute", files: [ { templateId: "dev-notes", name: "Development Notes", priority: "recommended", description: "Implementation details" }, { templateId: "test-plan", name: "Test Plan", priority: "optional", description: "Testing strategy" }, { templateId: "implementation-log", name: "Implementation Log", priority: "optional", description: "Progress log" }, ], requiresRepository: true, requiresTasks: true, guidance: "Execute tasks and track implementation progress.", }, review: { phase: "review", files: [ { templateId: "release-notes", name: "Release Notes", priority: "required", description: "Changes for release" }, { templateId: "review-checklist", name: "Review Checklist", priority: "recommended", description: "Code and feature review" }, { templateId: "retrospective", name: "Retrospective", priority: "optional", description: "Project learnings" }, ], requiresRepository: false, requiresTasks: false, guidance: "Review work and document the release.", }, }; interface DeliverableStatus { templateId: string; name: string; priority: "required" | "recommended" | "optional"; description: string; completed: boolean; fileId?: string; actualName?: string; } interface PhaseDeliverablesProps { contract: ContractWithRelations; onCreateFile?: (templateId: string, suggestedName: string) => void; } export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDeliverablesProps) { const deliverables = PHASE_DELIVERABLES[contract.phase]; // Calculate deliverable status const fileStatuses = useMemo((): DeliverableStatus[] => { return deliverables.files.map((rec) => { // Find matching file by name similarity const matchedFile = contract.files.find((f) => { const nameLower = f.name.toLowerCase(); const recLower = rec.name.toLowerCase(); return ( f.contractPhase === contract.phase && (nameLower.includes(recLower) || recLower.includes(nameLower) || nameLower.includes(rec.templateId.replace("-", " "))) ); }); return { ...rec, completed: !!matchedFile, fileId: matchedFile?.id, actualName: matchedFile?.name, }; }); }, [contract.files, contract.phase, deliverables.files]); // Check repository status const hasRepository = contract.repositories.length > 0; // Check task status const taskStats = useMemo(() => { const total = contract.tasks.length; const done = contract.tasks.filter((t) => t.status === "done" || t.status === "merged").length; const pending = contract.tasks.filter((t) => t.status === "pending").length; const running = contract.tasks.filter((t) => ["running", "initializing", "starting"].includes(t.status)).length; const failed = contract.tasks.filter((t) => t.status === "failed").length; return { total, done, pending, running, failed }; }, [contract.tasks]); // Calculate completion percentage const completionPercent = useMemo(() => { let completed = 0; let total = 0; // Count required and recommended files fileStatuses.forEach((s) => { if (s.priority !== "optional") { total++; if (s.completed) completed++; } }); // Count repository if required if (deliverables.requiresRepository) { total++; if (hasRepository) completed++; } // Count tasks if in execute phase if (deliverables.requiresTasks && taskStats.total > 0) { total++; if (taskStats.done === taskStats.total) completed++; } return total > 0 ? Math.round((completed / total) * 100) : 100; }, [fileStatuses, hasRepository, deliverables, taskStats]); const priorityColors = { required: "text-red-400", recommended: "text-yellow-400", optional: "text-[#555]", }; return (

Phase Deliverables

{completionPercent}%
{/* Guidance text */}

{deliverables.guidance}

{/* File deliverables */}
{fileStatuses.map((status) => (
{status.completed ? "[+]" : "[ ]"}
{status.completed ? status.actualName : status.name} {!status.completed && ( {status.priority} )}
{status.description}
{!status.completed && onCreateFile && ( )}
))}
{/* Repository status */} {deliverables.requiresRepository && (
{hasRepository ? "[+]" : "[ ]"}
Repository Configured {!hasRepository && ( required )}
)} {/* Task status (execute phase) */} {deliverables.requiresTasks && (
0 && taskStats.done === taskStats.total ? "border-green-400/20 bg-green-400/5" : "border-[rgba(117,170,252,0.15)]" }`} >
0 && taskStats.done === taskStats.total ? "text-green-400" : "text-[#555]" }`} > {taskStats.total > 0 && taskStats.done === taskStats.total ? "[+]" : "[ ]"} Tasks Completed
{taskStats.total > 0 ? ( {taskStats.done}/{taskStats.total} {taskStats.running > 0 && ` (${taskStats.running} running)`} {taskStats.failed > 0 && ( ({taskStats.failed} failed) )} ) : ( No tasks yet )}
)}
); }