diff options
Diffstat (limited to 'makima/frontend/src/components/workflow')
3 files changed, 230 insertions, 0 deletions
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 ( + <div + className={` + flex flex-col min-w-[220px] flex-1 border border-[rgba(117,170,252,0.15)] + ${isDragOver ? "bg-[rgba(117,170,252,0.05)]" : "bg-transparent"} + transition-colors + `} + onDragOver={handleDragOver} + onDragLeave={handleDragLeave} + onDrop={handleDrop} + > + {/* Column header */} + <div + className={` + p-3 border-b ${config.borderColor} ${config.bgColor} + flex items-center justify-between + `} + > + <span className={`font-mono text-xs uppercase tracking-wider ${config.color}`}> + {config.label} + </span> + <span className="font-mono text-[10px] text-[#555]"> + ({contracts.length}) + </span> + </div> + + {/* Cards container */} + <div className="flex-1 overflow-y-auto p-2 space-y-2"> + {contracts.length === 0 ? ( + <div className="p-4 text-center font-mono text-[10px] text-[#555]"> + No contracts + </div> + ) : ( + contracts.map((contract) => ( + <WorkflowContractCard + key={contract.id} + contract={contract} + onClick={() => onContractClick(contract.id)} + onDragStart={(e) => { + e.dataTransfer.setData("contractId", contract.id); + e.dataTransfer.effectAllowed = "move"; + }} + /> + )) + )} + </div> + </div> + ); +} 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<ContractPhase, ContractSummary[]> = { + 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 ( + <div className="flex gap-2 h-full overflow-x-auto"> + {phases.map((phase) => ( + <PhaseColumn + key={phase} + phase={phase} + contracts={contractsByPhase[phase]} + onContractClick={onContractClick} + onDrop={onPhaseChange} + /> + ))} + </div> + ); +} 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<ContractStatus, { label: string; color: string }> = { + 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 ( + <div + draggable + onDragStart={onDragStart} + onClick={onClick} + className="p-3 bg-[rgba(9,13,20,0.8)] border border-[rgba(117,170,252,0.2)] hover:border-[rgba(117,170,252,0.4)] cursor-pointer transition-colors select-none" + > + {/* Name */} + <div className="font-mono text-sm text-[#dbe7ff] truncate mb-1"> + {contract.name} + </div> + + {/* Status and counts row */} + <div className="flex items-center justify-between"> + <span className={`font-mono text-[10px] uppercase ${status.color}`}> + {status.label} + </span> + <div className="flex items-center gap-2 font-mono text-[10px] text-[#555]"> + <span title="Files">{contract.fileCount} files</span> + <span title="Tasks">{contract.taskCount} tasks</span> + </div> + </div> + + {/* Description preview if exists */} + {contract.description && ( + <div className="mt-1 font-mono text-[10px] text-[#555] truncate"> + {contract.description} + </div> + )} + </div> + ); +} |
