diff options
Diffstat (limited to 'frontend/src/components/document/nodes/ContractBlockComponent.tsx')
| -rw-r--r-- | frontend/src/components/document/nodes/ContractBlockComponent.tsx | 117 |
1 files changed, 117 insertions, 0 deletions
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<string, string> = { + planning: '#3b82f6', + execution: '#f59e0b', + review: '#8b5cf6', + completed: '#10b981', + failed: '#ef4444', +}; + +const STATUS_COLORS: Record<string, string> = { + active: '#10b981', + running: '#10b981', + idle: '#f59e0b', + paused: '#f59e0b', + completed: '#10b981', + failed: '#ef4444', + archived: '#6b7280', +}; + +export function ContractBlockComponent({ contractId, contractName }: ContractBlockComponentProps) { + const [contract, setContract] = useState<ContractInfo | null>(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState<string | null>(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 ( + <div className="contract-block" contentEditable={false}> + <div className="contract-block-loading"> + <div className="contract-block-spinner" /> + <span>Loading contract...</span> + </div> + </div> + ); + } + + if (error) { + return ( + <div className="contract-block contract-block--error" contentEditable={false}> + <div className="contract-block-header"> + <span className="contract-block-icon">📦</span> + <span className="contract-block-name">{contractName}</span> + </div> + <div className="contract-block-error-msg">Unable to load: {error}</div> + </div> + ); + } + + 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 ( + <div className="contract-block" contentEditable={false}> + <div className="contract-block-header"> + <span className="contract-block-icon">📦</span> + <span className="contract-block-name">{contract?.name || contractName}</span> + <span + className="contract-block-phase-badge" + style={{ backgroundColor: phaseColor + '20', color: phaseColor }} + > + {phase} + </span> + <span + className="contract-block-status-dot" + style={{ backgroundColor: statusColor }} + title={status} + /> + </div> + {contract?.contract_type && ( + <div className="contract-block-meta"> + <span className="contract-block-type">{contract.contract_type}</span> + </div> + )} + </div> + ); +} |
