From 87044a747b47bd83249d61a45842c7f7b2eae56d Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 11 Jan 2026 05:52:14 +0000 Subject: Contract system --- .../src/components/workflow/PhaseColumn.tsx | 123 +++++++++++++++++++++ .../src/components/workflow/WorkflowBoard.tsx | 54 +++++++++ .../components/workflow/WorkflowContractCard.tsx | 53 +++++++++ 3 files changed, 230 insertions(+) create mode 100644 makima/frontend/src/components/workflow/PhaseColumn.tsx create mode 100644 makima/frontend/src/components/workflow/WorkflowBoard.tsx create mode 100644 makima/frontend/src/components/workflow/WorkflowContractCard.tsx (limited to 'makima/frontend/src/components/workflow') diff --git a/makima/frontend/src/components/workflow/PhaseColumn.tsx b/makima/frontend/src/components/workflow/PhaseColumn.tsx new file mode 100644 index 0000000..ddea85f --- /dev/null +++ b/makima/frontend/src/components/workflow/PhaseColumn.tsx @@ -0,0 +1,123 @@ +import { useState } from "react"; +import type { ContractSummary, ContractPhase } from "../../lib/api"; +import { WorkflowContractCard } from "./WorkflowContractCard"; + +interface PhaseColumnProps { + phase: ContractPhase; + contracts: ContractSummary[]; + onContractClick: (contractId: string) => void; + onDrop: (contractId: string, phase: ContractPhase) => void; +} + +const phaseConfig: Record< + ContractPhase, + { label: string; color: string; bgColor: string; borderColor: string } +> = { + research: { + label: "Research", + color: "text-purple-400", + bgColor: "bg-purple-400/10", + borderColor: "border-purple-400/30", + }, + specify: { + label: "Specify", + color: "text-blue-400", + bgColor: "bg-blue-400/10", + borderColor: "border-blue-400/30", + }, + plan: { + label: "Plan", + color: "text-cyan-400", + bgColor: "bg-cyan-400/10", + borderColor: "border-cyan-400/30", + }, + execute: { + label: "Execute", + color: "text-yellow-400", + bgColor: "bg-yellow-400/10", + borderColor: "border-yellow-400/30", + }, + review: { + label: "Review", + color: "text-green-400", + bgColor: "bg-green-400/10", + borderColor: "border-green-400/30", + }, +}; + +export function PhaseColumn({ + phase, + contracts, + onContractClick, + onDrop, +}: PhaseColumnProps) { + const [isDragOver, setIsDragOver] = useState(false); + const config = phaseConfig[phase]; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(true); + }; + + const handleDragLeave = () => { + setIsDragOver(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + const contractId = e.dataTransfer.getData("contractId"); + if (contractId) { + onDrop(contractId, phase); + } + }; + + return ( +
+ {/* Column header */} +
+ + {config.label} + + + ({contracts.length}) + +
+ + {/* Cards container */} +
+ {contracts.length === 0 ? ( +
+ No contracts +
+ ) : ( + contracts.map((contract) => ( + onContractClick(contract.id)} + onDragStart={(e) => { + e.dataTransfer.setData("contractId", contract.id); + e.dataTransfer.effectAllowed = "move"; + }} + /> + )) + )} +
+
+ ); +} diff --git a/makima/frontend/src/components/workflow/WorkflowBoard.tsx b/makima/frontend/src/components/workflow/WorkflowBoard.tsx new file mode 100644 index 0000000..af4aec7 --- /dev/null +++ b/makima/frontend/src/components/workflow/WorkflowBoard.tsx @@ -0,0 +1,54 @@ +import { useMemo } from "react"; +import type { ContractSummary, ContractPhase } from "../../lib/api"; +import { PhaseColumn } from "./PhaseColumn"; + +interface WorkflowBoardProps { + contracts: ContractSummary[]; + onContractClick: (contractId: string) => void; + onPhaseChange: (contractId: string, newPhase: ContractPhase) => void; +} + +const phases: ContractPhase[] = ["research", "specify", "plan", "execute", "review"]; + +export function WorkflowBoard({ + contracts, + onContractClick, + onPhaseChange, +}: WorkflowBoardProps) { + // Group contracts by phase + const contractsByPhase = useMemo(() => { + const grouped: Record = { + research: [], + specify: [], + plan: [], + execute: [], + review: [], + }; + + for (const contract of contracts) { + const phase = contract.phase as ContractPhase; + if (grouped[phase]) { + grouped[phase].push(contract); + } else { + // Default to research if unknown phase + grouped.research.push(contract); + } + } + + return grouped; + }, [contracts]); + + return ( +
+ {phases.map((phase) => ( + + ))} +
+ ); +} diff --git a/makima/frontend/src/components/workflow/WorkflowContractCard.tsx b/makima/frontend/src/components/workflow/WorkflowContractCard.tsx new file mode 100644 index 0000000..e6c8a1c --- /dev/null +++ b/makima/frontend/src/components/workflow/WorkflowContractCard.tsx @@ -0,0 +1,53 @@ +import type { ContractSummary, ContractStatus } from "../../lib/api"; + +interface WorkflowContractCardProps { + contract: ContractSummary; + onClick: () => void; + onDragStart: (e: React.DragEvent) => void; +} + +const statusConfig: Record = { + active: { label: "Active", color: "text-green-400" }, + completed: { label: "Done", color: "text-blue-400" }, + archived: { label: "Archived", color: "text-[#555]" }, +}; + +export function WorkflowContractCard({ + contract, + onClick, + onDragStart, +}: WorkflowContractCardProps) { + const status = statusConfig[contract.status] || statusConfig.active; + + return ( +
+ {/* Name */} +
+ {contract.name} +
+ + {/* Status and counts row */} +
+ + {status.label} + +
+ {contract.fileCount} files + {contract.taskCount} tasks +
+
+ + {/* Description preview if exists */} + {contract.description && ( +
+ {contract.description} +
+ )} +
+ ); +} -- cgit v1.2.3