diff options
| author | soryu <soryu@soryu.co> | 2026-01-11 05:52:14 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-15 00:21:16 +0000 |
| commit | 87044a747b47bd83249d61a45842c7f7b2eae56d (patch) | |
| tree | ef2000ce79ffcc2723ef841acef5aa1deb1d5378 /makima/frontend/src/components/contracts/ContractList.tsx | |
| parent | 077820c4167c168072d217a1b01df840463a12a8 (diff) | |
| download | soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.tar.gz soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.zip | |
Contract system
Diffstat (limited to 'makima/frontend/src/components/contracts/ContractList.tsx')
| -rw-r--r-- | makima/frontend/src/components/contracts/ContractList.tsx | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/makima/frontend/src/components/contracts/ContractList.tsx b/makima/frontend/src/components/contracts/ContractList.tsx new file mode 100644 index 0000000..3a7b163 --- /dev/null +++ b/makima/frontend/src/components/contracts/ContractList.tsx @@ -0,0 +1,176 @@ +import { useState } from "react"; +import type { ContractSummary, ContractStatus } from "../../lib/api"; +import { PhaseBadge } from "./PhaseBadge"; +import { PhaseProgressBarCompact } from "./PhaseProgressBar"; + +interface ContractListProps { + contracts: ContractSummary[]; + loading: boolean; + onSelect: (id: string) => void; + onCreate: () => void; + selectedId?: string; +} + +const statusColors: Record<ContractStatus, string> = { + active: "text-green-400", + completed: "text-blue-400", + archived: "text-[#555]", +}; + +export function ContractList({ + contracts, + loading, + onSelect, + onCreate, + selectedId, +}: ContractListProps) { + const [filter, setFilter] = useState<ContractStatus | "all">("all"); + + const filteredContracts = + filter === "all" + ? contracts + : contracts.filter((c) => c.status === filter); + + if (loading) { + return ( + <div className="panel h-full flex items-center justify-center"> + <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div> + </div> + ); + } + + return ( + <div className="panel h-full flex flex-col"> + {/* Header */} + <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]"> + <div className="flex items-center justify-between mb-3"> + <h2 className="font-mono text-sm text-[#75aafc] uppercase tracking-wider"> + Contracts + </h2> + <button + onClick={onCreate} + className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase" + > + + New + </button> + </div> + + {/* Filter tabs */} + <div className="flex gap-1"> + {(["all", "active", "completed", "archived"] as const).map((status) => ( + <button + key={status} + onClick={() => setFilter(status)} + className={` + px-2 py-1 font-mono text-[10px] uppercase tracking-wider transition-colors + ${ + filter === status + ? "bg-[rgba(117,170,252,0.1)] text-[#9bc3ff] border border-[rgba(117,170,252,0.3)]" + : "text-[#555] hover:text-[#75aafc]" + } + `} + > + {status} + </button> + ))} + </div> + </div> + + {/* Contract list */} + <div className="flex-1 overflow-y-auto"> + {filteredContracts.length === 0 ? ( + <div className="p-4 text-center"> + <p className="font-mono text-sm text-[#555]"> + {filter === "all" + ? "No contracts yet" + : `No ${filter} contracts`} + </p> + </div> + ) : ( + <div className="divide-y divide-[rgba(117,170,252,0.15)]"> + {filteredContracts.map((contract) => ( + <button + key={contract.id} + onClick={() => onSelect(contract.id)} + className={` + w-full text-left p-4 transition-colors + ${ + selectedId === contract.id + ? "bg-[rgba(117,170,252,0.1)]" + : "hover:bg-[rgba(117,170,252,0.05)]" + } + `} + > + <div className="flex items-start justify-between gap-2 mb-2"> + <h3 className="font-mono text-sm text-[#dbe7ff] truncate"> + {contract.name} + </h3> + <span + className={`text-[10px] font-mono uppercase ${ + statusColors[contract.status] + }`} + > + {contract.status} + </span> + </div> + + {contract.description && ( + <p className="font-mono text-xs text-[#555] mb-2 line-clamp-2"> + {contract.description} + </p> + )} + + <div className="flex items-center justify-between"> + <PhaseProgressBarCompact currentPhase={contract.phase} /> + <div className="flex items-center gap-3 text-[10px] font-mono text-[#555]"> + {contract.fileCount > 0 && ( + <span>{contract.fileCount} files</span> + )} + {contract.taskCount > 0 && ( + <span>{contract.taskCount} tasks</span> + )} + {contract.repositoryCount > 0 && ( + <span>{contract.repositoryCount} repos</span> + )} + </div> + </div> + </button> + ))} + </div> + )} + </div> + </div> + ); +} + +export function ContractCard({ + contract, + onClick, +}: { + contract: ContractSummary; + onClick: () => void; +}) { + return ( + <button + onClick={onClick} + className="w-full text-left p-4 border border-[rgba(117,170,252,0.2)] hover:border-[rgba(117,170,252,0.4)] transition-colors" + > + <div className="flex items-start justify-between gap-2 mb-2"> + <h3 className="font-mono text-sm text-[#dbe7ff]">{contract.name}</h3> + <PhaseBadge phase={contract.phase} /> + </div> + + {contract.description && ( + <p className="font-mono text-xs text-[#555] mb-3 line-clamp-2"> + {contract.description} + </p> + )} + + <div className="flex items-center gap-4 text-[10px] font-mono text-[#555]"> + <span>{contract.fileCount} files</span> + <span>{contract.taskCount} tasks</span> + <span>{contract.repositoryCount} repos</span> + </div> + </button> + ); +} |
