import React, { useEffect, useState } from 'react'; interface SessionProgress { sessionId: string; currentStep: number; state: string; completedSteps: number[]; hasError: boolean; errorMessage: string | null; } interface SessionProgressMonitorProps { sessionId: string | null; progress: SessionProgress | null; isRunning: boolean; } const STEP_NAMES: { [key: number]: string } = { 1: 'Navigate to Hosted Racing', 2: 'Click Create a Race', 3: 'Fill Race Information', 4: 'Configure Server Details', 5: 'Set Admins', 6: 'Add Admin', 7: 'Set Time Limits', 8: 'Set Cars', 9: 'Add Car', 10: 'Set Car Classes', 11: 'Set Track', 12: 'Add Track', 13: 'Configure Track Options', 14: 'Set Time of Day', 15: 'Configure Weather', 16: 'Set Race Options', 17: 'Set Track Conditions' }; export function SessionProgressMonitor({ sessionId, progress, isRunning }: SessionProgressMonitorProps) { const [ackStatusByStep, setAckStatusByStep] = useState>({}); const [automationEventMsg, setAutomationEventMsg] = useState(null); const getStateColor = (state: string) => { switch (state) { case 'IN_PROGRESS': return '#0066cc'; case 'COMPLETED': return '#28a745'; case 'FAILED': return '#dc3545'; case 'STOPPED_AT_STEP_18': return '#ffc107'; default: return '#6c757d'; } }; const getStateLabel = (state: string) => { switch (state) { case 'IN_PROGRESS': return 'Running'; case 'COMPLETED': return 'Completed'; case 'FAILED': return 'Failed'; case 'STOPPED_AT_STEP_18': return 'Stopped at Step 18'; default: return state; } }; // Request overlay action when the current step changes (gate overlay rendering on ack) useEffect(() => { if (!progress || !sessionId) return; const currentStep = progress.currentStep; const action = { id: `${progress.sessionId}-${currentStep}`, label: STEP_NAMES[currentStep] || `Step ${currentStep}`, meta: {}, timeoutMs: 1000 }; let mounted = true; (async () => { try { // Use electronAPI overlayActionRequest to obtain ack if (window.electronAPI?.overlayActionRequest) { const ack = await window.electronAPI.overlayActionRequest(action); if (!mounted) return; setAckStatusByStep(prev => ({ ...prev, [currentStep]: ack.status })); } else { // If no IPC available, mark tentative as fallback setAckStatusByStep(prev => ({ ...prev, [currentStep]: 'tentative' })); } } catch (e) { if (!mounted) return; setAckStatusByStep(prev => ({ ...prev, [currentStep]: 'failed' })); } })(); return () => { mounted = false; }; }, [progress?.currentStep, sessionId]); // Subscribe to automation events for optional live updates useEffect(() => { if (window.electronAPI?.onAutomationEvent) { const off = window.electronAPI.onAutomationEvent((ev) => { if (ev && ev.payload && ev.payload.actionId && ev.type) { setAutomationEventMsg(`${ev.type} ${ev.payload.actionId}`); } else if (ev && ev.type) { setAutomationEventMsg(ev.type); } }); return () => { if (typeof off === 'function') off(); }; } return; }, []); if (!sessionId && !isRunning) { return (

Session Progress

Configure and start an automation session to see progress here.

); } return (

Session Progress

{sessionId && (
Session ID
{sessionId}
)} {progress && ( <>
Status
{getStateLabel(progress.state)}
{progress.state === 'STOPPED_AT_STEP_18' && (
⚠️ Safety Stop

Automation stopped at step 18 (Track Conditions) as configured. This prevents accidental session creation during POC demonstration.

)} {progress.hasError && progress.errorMessage && (
Error

{progress.errorMessage}

)}
Progress: {progress.completedSteps.length} / 17 steps
{Object.entries(STEP_NAMES).map(([stepNum, stepName]) => { const step = parseInt(stepNum); const isCompleted = progress.completedSteps.includes(step); const isCurrent = progress.currentStep === step; return (
{isCompleted ? '✓' : step}
{stepName}
{isCurrent && (
IN PROGRESS
)}
); })}
)}
); }