diff options
Diffstat (limited to 'frontend/src/components/document/nodes/StepsDiagramComponent.tsx')
| -rw-r--r-- | frontend/src/components/document/nodes/StepsDiagramComponent.tsx | 239 |
1 files changed, 0 insertions, 239 deletions
diff --git a/frontend/src/components/document/nodes/StepsDiagramComponent.tsx b/frontend/src/components/document/nodes/StepsDiagramComponent.tsx deleted file mode 100644 index ac1cb83..0000000 --- a/frontend/src/components/document/nodes/StepsDiagramComponent.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import React, { useEffect, useState, useRef, useCallback } from 'react'; -import { getDirective, DirectiveStep, DirectiveWithSteps } from '../../../services/directiveApi'; -import { StepLogFeed } from './StepLogFeed'; -import './StepsDiagram.css'; - -interface StepsDiagramComponentProps { - directiveId: string; - onExpandContract?: (step: DirectiveStep) => void; -} - -type StepStatus = 'pending' | 'ready' | 'running' | 'completed' | 'failed' | 'skipped'; - -const STATUS_LABELS: Record<string, string> = { - pending: 'Queued', - ready: 'Ready', - running: 'Executing', - completed: 'Fulfilled', - failed: 'Failed', - skipped: 'Skipped', -}; - -function formatTime(dateStr: string): string { - if (!dateStr) return ''; - const d = new Date(dateStr); - return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); -} - -interface StepCardProps { - step: DirectiveStep; - isExpanded: boolean; - onToggleExpand: () => void; - onCollapse: () => void; -} - -function StepCard({ step, isExpanded, onToggleExpand, onCollapse }: StepCardProps) { - const status = (step.status || 'pending').toLowerCase() as StepStatus; - const hasTask = !!step.taskId || !!step.contractId; - const canExpand = hasTask && ['running', 'completed', 'failed'].includes(status); - - return ( - <div className={`steps-diagram-card steps-diagram-card--${status} ${isExpanded ? 'steps-diagram-card--expanded' : ''}`}> - <div - className={`steps-diagram-card-header ${canExpand ? 'steps-diagram-card-header--clickable' : ''}`} - onClick={canExpand ? onToggleExpand : undefined} - role={canExpand ? 'button' : undefined} - tabIndex={canExpand ? 0 : undefined} - onKeyDown={canExpand ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onToggleExpand(); } } : undefined} - > - <span className="steps-diagram-card-name">{step.name}</span> - <div className="steps-diagram-card-header-right"> - <span className={`steps-diagram-status-badge steps-diagram-status-badge--${status}`}> - {STATUS_LABELS[status] || status} - </span> - {canExpand && ( - <span className={`steps-diagram-expand-icon ${isExpanded ? 'expanded' : ''}`}> - ▶ - </span> - )} - </div> - </div> - {step.description && !isExpanded && ( - <p className="steps-diagram-card-desc">{step.description}</p> - )} - <div className="steps-diagram-card-footer"> - <span className="steps-diagram-card-index">#{step.orderIndex}</span> - {status === 'running' && ( - <span className="steps-diagram-card-progress">In progress...</span> - )} - {status === 'completed' && step.completedAt && ( - <span className="steps-diagram-card-time"> - Completed {formatTime(step.completedAt)} - </span> - )} - </div> - - {/* Expandable log feed */} - {isExpanded && hasTask && ( - <StepLogFeed - taskId={step.taskId || step.contractId} - stepName={step.name} - stepStatus={status} - onCollapse={onCollapse} - /> - )} - </div> - ); -} - -export function StepsDiagramComponent({ directiveId, onExpandContract }: StepsDiagramComponentProps) { - const [steps, setSteps] = useState<DirectiveStep[]>([]); - const [directiveStatus, setDirectiveStatus] = useState<string>(''); - const [loading, setLoading] = useState(true); - const [error, setError] = useState<string | null>(null); - const [expandedStepId, setExpandedStepId] = useState<string | null>(null); - const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null); - const prevStepCountRef = useRef(0); - - const fetchSteps = useCallback(async () => { - try { - const data: DirectiveWithSteps = await getDirective(directiveId); - setSteps(data.steps || []); - setDirectiveStatus(data.status || ''); - setError(null); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load contracts'); - } finally { - setLoading(false); - } - }, [directiveId]); - - useEffect(() => { - fetchSteps(); - intervalRef.current = setInterval(fetchSteps, 5000); - return () => { - if (intervalRef.current) clearInterval(intervalRef.current); - }; - }, [fetchSteps]); - - // Track when new steps appear for animation - useEffect(() => { - prevStepCountRef.current = steps.length; - }, [steps.length]); - - // Keyboard shortcut: Escape to collapse expanded step - useEffect(() => { - const handler = (e: KeyboardEvent) => { - if (e.key === 'Escape' && expandedStepId) { - setExpandedStepId(null); - } - }; - document.addEventListener('keydown', handler); - return () => document.removeEventListener('keydown', handler); - }, [expandedStepId]); - - const toggleExpand = useCallback((stepId: string) => { - setExpandedStepId(prev => prev === stepId ? null : stepId); - }, []); - - const collapseExpanded = useCallback(() => { - setExpandedStepId(null); - }, []); - - const completedCount = steps.filter(s => s.status?.toLowerCase() === 'completed').length; - const totalCount = steps.length; - const isActive = ['active', 'running', 'planning'].includes(directiveStatus.toLowerCase()); - const isBuilding = isActive && steps.length === 0; - - // Group steps by orderIndex - const groupedSteps: Map<number, DirectiveStep[]> = new Map(); - const sortedSteps = [...steps].sort((a, b) => a.orderIndex - b.orderIndex); - for (const step of sortedSteps) { - const idx = step.orderIndex; - if (!groupedSteps.has(idx)) groupedSteps.set(idx, []); - groupedSteps.get(idx)!.push(step); - } - const orderGroups = Array.from(groupedSteps.entries()).sort((a, b) => a[0] - b[0]); - - if (loading) { - return ( - <div className="steps-diagram" contentEditable={false}> - <div className="steps-diagram-header"> - <span className="steps-diagram-header-title">Contract Steps</span> - <span className="steps-diagram-header-author">Authored by Makima</span> - </div> - <div className="steps-diagram-loading"> - <div className="steps-diagram-spinner" /> - <span>Loading contracts...</span> - </div> - </div> - ); - } - - if (error) { - return ( - <div className="steps-diagram" contentEditable={false}> - <div className="steps-diagram-header"> - <span className="steps-diagram-header-title">Contract Steps</span> - <span className="steps-diagram-header-author">Authored by Makima</span> - </div> - <div className="steps-diagram-error">Failed to load contracts: {error}</div> - </div> - ); - } - - return ( - <div className={`steps-diagram ${expandedStepId ? 'steps-diagram--has-expanded' : ''}`} contentEditable={false}> - <div className="steps-diagram-header"> - <div className="steps-diagram-header-left"> - <span className="steps-diagram-header-title">Contract Steps</span> - {totalCount > 0 && ( - <span className="steps-diagram-header-count"> - {completedCount}/{totalCount} fulfilled - </span> - )} - </div> - <span className="steps-diagram-header-author">Authored by Makima</span> - </div> - - {isBuilding && ( - <div className="steps-diagram-planning"> - <div className="steps-diagram-planning-dots"> - <span /><span /><span /> - </div> - <span>Makima is drafting contracts...</span> - </div> - )} - - {totalCount === 0 && !isBuilding && ( - <div className="steps-diagram-empty">No contract steps defined yet.</div> - )} - - {totalCount > 0 && ( - <div className="steps-diagram-dag"> - {orderGroups.map(([orderIndex, groupSteps], groupIdx) => ( - <React.Fragment key={orderIndex}> - {groupIdx > 0 && ( - <div className="steps-diagram-arrow"> - <div className="steps-diagram-arrow-line" /> - <div className="steps-diagram-arrow-head" /> - </div> - )} - <div className="steps-diagram-group"> - {groupSteps.map((step) => ( - <StepCard - key={step.id} - step={step} - isExpanded={expandedStepId === step.id} - onToggleExpand={() => toggleExpand(step.id)} - onCollapse={collapseExpanded} - /> - ))} - </div> - </React.Fragment> - ))} - </div> - )} - </div> - ); -} |
