1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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>
);
}
|