From d513f93c84ae985738e0f696fcb72fa1153046ef Mon Sep 17 00:00:00 2001 From: soryu Date: Tue, 28 Apr 2026 17:35:08 +0100 Subject: feat: document UI with contract blocks, expandable logs, and interaction controls (#97) * feat: soryu-co/soryu - makima: Rename tasks to contracts in directive API and types * feat: soryu-co/soryu - makima: Add contract interaction panel with comment and interrupt * feat: soryu-co/soryu - makima: Build expandable contract log feed in StepsDiagram * feat: soryu-co/soryu - makima: Rename tasks to contracts throughout document UI and add contract block support * feat: soryu-co/soryu - makima: Add comment and interrupt controls to expanded step log feed * feat: soryu-co/soryu - makima: Audit and fix Document UI feature flag visibility and missing implementations * feat: soryu-co/soryu - makima: Add expandable step rows with live log feed in StepsDiagram * WIP: heartbeat checkpoint * feat: soryu-co/soryu - makima: Integrate all document UI components and final polish --- .../document/nodes/ContractBlockComponent.tsx | 117 +++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 frontend/src/components/document/nodes/ContractBlockComponent.tsx (limited to 'frontend/src/components/document/nodes/ContractBlockComponent.tsx') diff --git a/frontend/src/components/document/nodes/ContractBlockComponent.tsx b/frontend/src/components/document/nodes/ContractBlockComponent.tsx new file mode 100644 index 0000000..0d9a25a --- /dev/null +++ b/frontend/src/components/document/nodes/ContractBlockComponent.tsx @@ -0,0 +1,117 @@ +import React, { useEffect, useState } from 'react'; +import './ContractBlock.css'; + +interface ContractBlockComponentProps { + contractId: string; + contractName: string; +} + +interface ContractInfo { + id: string; + name: string; + status: string; + phase: string; + contract_type: string; +} + +const PHASE_COLORS: Record = { + planning: '#3b82f6', + execution: '#f59e0b', + review: '#8b5cf6', + completed: '#10b981', + failed: '#ef4444', +}; + +const STATUS_COLORS: Record = { + active: '#10b981', + running: '#10b981', + idle: '#f59e0b', + paused: '#f59e0b', + completed: '#10b981', + failed: '#ef4444', + archived: '#6b7280', +}; + +export function ContractBlockComponent({ contractId, contractName }: ContractBlockComponentProps) { + const [contract, setContract] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + + async function fetchContract() { + try { + const response = await fetch(`/api/v1/contracts/${contractId}`); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const data = await response.json(); + if (!cancelled) { + setContract(data.contract || data); + setError(null); + } + } catch (err) { + if (!cancelled) { + setError(err instanceof Error ? err.message : 'Failed to load'); + } + } finally { + if (!cancelled) setLoading(false); + } + } + + fetchContract(); + return () => { cancelled = true; }; + }, [contractId]); + + if (loading) { + return ( +
+
+
+ Loading contract... +
+
+ ); + } + + if (error) { + return ( +
+
+ 📦 + {contractName} +
+
Unable to load: {error}
+
+ ); + } + + const phase = contract?.phase?.toLowerCase() || 'unknown'; + const status = contract?.status?.toLowerCase() || 'unknown'; + const phaseColor = PHASE_COLORS[phase] || '#6b7280'; + const statusColor = STATUS_COLORS[status] || '#6b7280'; + + return ( +
+
+ 📦 + {contract?.name || contractName} + + {phase} + + +
+ {contract?.contract_type && ( +
+ {contract.contract_type} +
+ )} +
+ ); +} -- cgit v1.2.3