/** * Global Progress Bar Component * Shows full pipeline progress across all 7 automation stages. * Persists until 100% complete or run is finished. */ import React from 'react'; import { AutomationRun, InitialSnapshot, StageProgress, GlobalProgress } from '../../services/automationService'; import { BoltIcon, CheckCircleIcon, PauseIcon } from '../../icons'; // Stage colors matching AutomationPage STAGE_CONFIG exactly const STAGE_COLORS = [ 'from-brand-500 to-brand-600', // Stage 1: Keywords → Clusters (brand/teal) 'from-purple-500 to-purple-600', // Stage 2: Clusters → Ideas (purple) 'from-warning-500 to-warning-600', // Stage 3: Ideas → Tasks (amber) 'from-brand-500 to-brand-600', // Stage 4: Tasks → Content (brand/teal) 'from-success-500 to-success-600', // Stage 5: Content → Image Prompts (green) 'from-purple-500 to-purple-600', // Stage 6: Image Prompts → Images (purple) 'from-success-500 to-success-600', // Stage 7: Review Gate (green) ]; const STAGE_NAMES = [ 'Keywords', 'Clusters', 'Ideas', 'Tasks', 'Content', 'Prompts', 'Publish', ]; // Helper to get processed count from stage result using correct key export const getProcessedFromResult = (result: any, stageNumber: number): number => { if (!result) return 0; const keyMap: Record = { 1: 'keywords_processed', 2: 'clusters_processed', 3: 'ideas_processed', 4: 'tasks_processed', 5: 'content_processed', 6: 'images_processed', 7: 'ready_for_review' }; return result[keyMap[stageNumber]] ?? 0; }; interface GlobalProgressBarProps { currentRun: AutomationRun | null; globalProgress?: GlobalProgress | null; stages?: StageProgress[]; initialSnapshot?: InitialSnapshot | null; } const GlobalProgressBar: React.FC = ({ currentRun, globalProgress, stages, initialSnapshot, }) => { // Don't render if no run or run is completed with 100% if (!currentRun) { return null; } // Calculate global progress if not provided from API const calculateGlobalProgress = (): { percentage: number; completed: number; total: number } => { // If we have API-provided global progress, use it if (globalProgress) { return { percentage: globalProgress.percentage, completed: globalProgress.completed_items, total: globalProgress.total_items, }; } // Fallback: Calculate from currentRun and initialSnapshot const snapshot = initialSnapshot || (currentRun as any)?.initial_snapshot; // Calculate total completed from all stage results let totalCompleted = 0; for (let i = 1; i <= 7; i++) { const result = (currentRun as any)[`stage_${i}_result`]; if (result) { totalCompleted += getProcessedFromResult(result, i); } } // Calculate total items - sum of ALL stage initials from snapshot (updated after each stage) // This accounts for items created during the run (e.g., keywords create clusters, clusters create ideas) let totalItems = 0; if (snapshot) { for (let i = 1; i <= 7; i++) { const stageInitial = snapshot[`stage_${i}_initial`] || 0; totalItems += stageInitial; } } // Use the updated total from snapshot, or fallback to total_initial_items const finalTotal = totalItems > 0 ? totalItems : (snapshot?.total_initial_items || 0); // Ensure completed never exceeds total (clamp percentage to 100%) const percentage = finalTotal > 0 ? Math.round((totalCompleted / finalTotal) * 100) : 0; return { percentage: Math.min(percentage, 100), completed: totalCompleted, total: finalTotal, }; }; const { percentage: realPercent, completed, total } = calculateGlobalProgress(); // REMOVED: Animated progress that was causing confusion // Now using real percentage directly from backend const percentage = realPercent; // Hide if completed and at 100% if (currentRun.status === 'completed' && percentage >= 100) { return null; } const isPaused = currentRun.status === 'paused'; const currentStage = currentRun.current_stage; // Get stage status for segmented bar const getStageStatus = (stageNum: number): 'completed' | 'active' | 'pending' => { if (stages && stages[stageNum - 1]) { return stages[stageNum - 1].status as 'completed' | 'active' | 'pending'; } if (currentStage > stageNum) return 'completed'; if (currentStage === stageNum) return 'active'; return 'pending'; }; const formatDuration = (): string => { if (!currentRun.started_at) return ''; const start = new Date(currentRun.started_at).getTime(); const now = Date.now(); const diffMs = now - start; const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); if (diffHours > 0) { return `${diffHours}h ${diffMins % 60}m`; } return `${diffMins}m`; }; return (
{/* Header Row */}
{isPaused ? ( ) : percentage >= 100 ? ( ) : ( )}
{isPaused ? 'Pipeline Paused' : 'Full Pipeline Progress'}
Stage {currentStage} of 7 • {formatDuration()}
{percentage}%
{/* Segmented Progress Bar - Taller & More Vibrant */}
{[1, 2, 3, 4, 5, 6, 7].map(stageNum => { const status = getStageStatus(stageNum); const stageColor = STAGE_COLORS[stageNum - 1]; return (
{/* Tooltip on hover */}
S{stageNum}: {STAGE_NAMES[stageNum - 1]} {status === 'completed' && ' ✓'} {status === 'active' && ' ●'}
); })}
{/* Footer Row - Larger Font for Stage Numbers */}
{completed} / {total} items processed
{[1, 2, 3, 4, 5, 6, 7].map(stageNum => { const status = getStageStatus(stageNum); return ( {stageNum} {status === 'completed' && '✓'} {status === 'active' && '●'} ); })}
); }; export default GlobalProgressBar;