summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/directives/DirectiveContractsTab.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/components/directives/DirectiveContractsTab.tsx')
-rw-r--r--makima/frontend/src/components/directives/DirectiveContractsTab.tsx131
1 files changed, 131 insertions, 0 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveContractsTab.tsx b/makima/frontend/src/components/directives/DirectiveContractsTab.tsx
new file mode 100644
index 0000000..59ebfc8
--- /dev/null
+++ b/makima/frontend/src/components/directives/DirectiveContractsTab.tsx
@@ -0,0 +1,131 @@
+import { useNavigate } from "react-router";
+import type {
+ DirectiveWithChains,
+ StepContractSummary,
+ ContractPhase,
+} from "../../lib/api";
+import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar";
+
+interface DirectiveContractsTabProps {
+ directive: DirectiveWithChains;
+}
+
+const statusColors: Record<string, string> = {
+ active: "text-green-400",
+ completed: "text-blue-400",
+ archived: "text-[#555]",
+};
+
+function ContractCard({
+ summary,
+ label,
+}: {
+ summary: StepContractSummary;
+ label: string;
+}) {
+ const navigate = useNavigate();
+
+ const progressPct =
+ summary.taskCount > 0
+ ? Math.round((summary.tasksDone / summary.taskCount) * 100)
+ : 0;
+
+ return (
+ <div
+ className="border border-dashed border-[rgba(117,170,252,0.25)] bg-[rgba(117,170,252,0.03)] p-3 cursor-pointer hover:bg-[rgba(117,170,252,0.06)] transition-colors"
+ onClick={() => navigate(`/contracts/${summary.id}`)}
+ >
+ <div className="flex items-center justify-between mb-1.5">
+ <div className="flex items-center gap-2 min-w-0">
+ <span className="font-mono text-[11px] text-[#dbe7ff] truncate">
+ {summary.name}
+ </span>
+ <span className="font-mono text-[9px] text-[#7788aa] uppercase shrink-0">
+ {summary.contractType}
+ </span>
+ </div>
+ <div className="flex items-center gap-2 shrink-0">
+ <span
+ className={`font-mono text-[9px] uppercase ${statusColors[summary.status] || "text-[#888]"}`}
+ >
+ {summary.status}
+ </span>
+ <span className="font-mono text-[9px] text-[#75aafc]">&rarr;</span>
+ </div>
+ </div>
+
+ <div className="flex items-center gap-2 mb-1.5">
+ <span className="font-mono text-[9px] text-[#7788aa] uppercase shrink-0">
+ {label}
+ </span>
+ <PhaseProgressBarCompact
+ currentPhase={summary.phase as ContractPhase}
+ />
+ </div>
+
+ {/* Task progress bar */}
+ <div className="flex items-center gap-2">
+ <div className="flex-1 h-1 bg-[rgba(117,170,252,0.1)] rounded-full overflow-hidden">
+ <div
+ className="h-full bg-[#3f6fb3] rounded-full transition-all"
+ style={{ width: `${progressPct}%` }}
+ />
+ </div>
+ <span className="font-mono text-[9px] text-[#7788aa] shrink-0">
+ {summary.tasksDone}/{summary.taskCount} tasks
+ </span>
+ </div>
+ </div>
+ );
+}
+
+export function DirectiveContractsTab({
+ directive,
+}: DirectiveContractsTabProps) {
+ // Collect all contract summaries
+ const contracts: { summary: StepContractSummary; label: string }[] = [];
+
+ if (directive.orchestratorContractSummary) {
+ contracts.push({
+ summary: directive.orchestratorContractSummary,
+ label: "Planning",
+ });
+ }
+
+ for (const chain of directive.chains) {
+ for (const step of chain.steps) {
+ if (step.contractSummary) {
+ contracts.push({
+ summary: step.contractSummary,
+ label: step.name,
+ });
+ }
+ }
+ }
+
+ if (contracts.length === 0) {
+ return (
+ <div className="text-center py-8">
+ <p className="font-mono text-xs text-[#7788aa]">
+ {directive.status === "draft"
+ ? "No contracts yet. Start the directive to begin planning."
+ : directive.status === "planning"
+ ? "Planning in progress... contracts will appear when steps are created."
+ : "No contracts associated with this directive."}
+ </p>
+ </div>
+ );
+ }
+
+ return (
+ <div className="space-y-2">
+ {contracts.map((c) => (
+ <ContractCard
+ key={c.summary.id}
+ summary={c.summary}
+ label={c.label}
+ />
+ ))}
+ </div>
+ );
+}