import { useMemo } from "react"; import type { ContractWithRelations, ContractPhase, ContractType } from "../../lib/api"; // Phase deliverables configuration (mirrors backend phase_guidance.rs) // IDs must match backend phase_guidance.rs exactly for mark_deliverable_complete interface PhaseDeliverable { id: string; // Must match backend deliverable ID name: string; priority: "required" | "recommended" | "optional"; description: string; } interface PhaseConfig { deliverables: PhaseDeliverable[]; requiresRepository: boolean; requiresTasks: boolean; guidance: string; } // Contract type specific deliverables (must match backend phase_guidance.rs) type ContractTypeDeliverables = Partial>; const CONTRACT_TYPE_DELIVERABLES: Record = { simple: { plan: { deliverables: [ { id: "plan-document", name: "Plan", priority: "required", description: "Implementation plan detailing the approach and tasks" }, ], requiresRepository: true, requiresTasks: false, guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.", }, execute: { deliverables: [ { id: "pull-request", name: "Pull Request", priority: "required", description: "Pull request with the implemented changes" }, ], requiresRepository: true, requiresTasks: true, guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks to finish the contract.", }, }, specification: { research: { deliverables: [ { id: "research-notes", name: "Research Notes", priority: "required", description: "Document findings and insights during research" }, ], requiresRepository: false, requiresTasks: false, guidance: "Focus on understanding the problem space and document your findings in the Research Notes before moving to Specify phase.", }, specify: { deliverables: [ { id: "requirements-document", name: "Requirements Document", priority: "required", description: "Define functional and non-functional requirements" }, ], requiresRepository: false, requiresTasks: false, guidance: "Define what needs to be built with clear requirements in the Requirements Document. Ensure specifications are detailed enough for planning.", }, plan: { deliverables: [ { id: "plan-document", name: "Plan", priority: "required", description: "Implementation plan detailing the approach and tasks" }, ], requiresRepository: true, requiresTasks: false, guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.", }, execute: { deliverables: [ { id: "pull-request", name: "Pull Request", priority: "required", description: "Pull request with the implemented changes" }, ], requiresRepository: true, requiresTasks: true, guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks before moving to Review phase.", }, review: { deliverables: [ { id: "release-notes", name: "Release Notes", priority: "required", description: "Document changes for release communication" }, ], requiresRepository: false, requiresTasks: false, guidance: "Review completed work and document the release in the Release Notes. The contract can be completed after review.", }, }, execute: { execute: { deliverables: [], // No deliverables for execute-only contract type requiresRepository: true, requiresTasks: true, guidance: "Execute the tasks directly. No deliverable documents are required for this contract type.", }, }, }; // Get phase config for a specific contract type and phase function getPhaseConfig(contractType: ContractType, phase: ContractPhase): PhaseConfig { const typeConfig = CONTRACT_TYPE_DELIVERABLES[contractType]; const phaseConfig = typeConfig?.[phase]; if (phaseConfig) { return phaseConfig; } // Fallback for unknown phase/type combinations return { deliverables: [], requiresRepository: false, requiresTasks: false, guidance: `Unknown phase "${phase}" for contract type "${contractType}"`, }; } interface DeliverableStatus { id: 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) { // Get phase config based on contract type AND phase const phaseConfig = useMemo( () => getPhaseConfig(contract.contractType, contract.phase), [contract.contractType, contract.phase] ); // Calculate deliverable status const deliverableStatuses = useMemo((): DeliverableStatus[] => { return phaseConfig.deliverables.map((deliverable) => { // Find matching file by name similarity const matchedFile = contract.files.find((f) => { const nameLower = f.name.toLowerCase(); const deliverableLower = deliverable.name.toLowerCase(); return ( f.contractPhase === contract.phase && (nameLower.includes(deliverableLower) || deliverableLower.includes(nameLower) || nameLower.includes(deliverable.id.replace("-", " "))) ); }); return { ...deliverable, completed: !!matchedFile, fileId: matchedFile?.id, actualName: matchedFile?.name, }; }); }, [contract.files, contract.phase, phaseConfig.deliverables]); // 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 deliverables deliverableStatuses.forEach((s) => { if (s.priority !== "optional") { total++; if (s.completed) completed++; } }); // Count repository if required if (phaseConfig.requiresRepository) { total++; if (hasRepository) completed++; } // Count tasks if required if (phaseConfig.requiresTasks && taskStats.total > 0) { total++; if (taskStats.done === taskStats.total) completed++; } return total > 0 ? Math.round((completed / total) * 100) : 100; }, [deliverableStatuses, hasRepository, phaseConfig, taskStats]); const priorityColors = { required: "text-red-400", recommended: "text-yellow-400", optional: "text-[#555]", }; return (

Phase Deliverables

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

{phaseConfig.guidance}

{/* Deliverables checklist */}
{deliverableStatuses.map((status) => (
{status.completed ? "[+]" : "[ ]"}
{status.completed ? status.actualName : status.name} {!status.completed && ( {status.priority} )}
{status.description}
{!status.completed && onCreateFile && ( )}
))}
{/* Repository status */} {phaseConfig.requiresRepository && (
{hasRepository ? "[+]" : "[ ]"}
Repository Configured {!hasRepository && ( required )}
)} {/* Task status */} {phaseConfig.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 )}
)}
); }