From 87044a747b47bd83249d61a45842c7f7b2eae56d Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 11 Jan 2026 05:52:14 +0000 Subject: Contract system --- .../contracts/PhaseDeliverablesPanel.tsx | 301 +++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx (limited to 'makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx') diff --git a/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx b/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx new file mode 100644 index 0000000..da5025b --- /dev/null +++ b/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx @@ -0,0 +1,301 @@ +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 + )} +
+ )} +
+ ); +} -- cgit v1.2.3