summaryrefslogtreecommitdiff
path: root/frontend/src/components/document/nodes/ContractBlockComponent.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/document/nodes/ContractBlockComponent.tsx')
-rw-r--r--frontend/src/components/document/nodes/ContractBlockComponent.tsx117
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">&#x1F4E6;</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">&#x1F4E6;</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>
+ );
+}