/** * Website Analysis Status Component * Compact terminal-themed component for displaying website analysis task status * with execution logs in expanded sections */ import React, { useState, useEffect } from 'react'; import { Box, Typography, IconButton, Tooltip, CircularProgress, Collapse, Table, TableBody, TableCell, TableHead, TableRow, Chip, Divider, Button, } from '@mui/material'; import { RefreshCw, CheckCircle, XCircle, AlertTriangle, Info, ChevronDown, ChevronUp, Globe, Users, } from 'lucide-react'; import { useAuth } from '@clerk/clerk-react'; import { getWebsiteAnalysisStatus, retryWebsiteAnalysis, getWebsiteAnalysisLogs, WebsiteAnalysisStatusResponse, WebsiteAnalysisTask, WebsiteAnalysisExecutionLog, WebsiteAnalysisLogsResponse, } from '../../api/websiteAnalysisMonitoring'; import { TerminalPaper, TerminalTypography, TerminalChip, TerminalChipSuccess, TerminalChipError, TerminalChipWarning, TerminalAlert, TerminalTableCell, TerminalTableRow, terminalColors, } from './terminalTheme'; interface WebsiteAnalysisStatusProps { compact?: boolean; } interface TaskLogs { [taskId: number]: { logs: WebsiteAnalysisExecutionLog[]; loading: boolean; error: string | null; }; } const WebsiteAnalysisStatus: React.FC = ({ compact = true }) => { const { userId } = useAuth(); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(null); const [error, setError] = useState(null); const [expandedTaskId, setExpandedTaskId] = useState(null); const [taskLogs, setTaskLogs] = useState({}); const [hoveredLogId, setHoveredLogId] = useState(null); const fetchStatus = async () => { if (!userId) return; try { setLoading(true); setError(null); const response = await getWebsiteAnalysisStatus(userId); setStatus(response); } catch (err: any) { setError(err.message || 'Failed to fetch website analysis status'); console.error('Error fetching website analysis status:', err); } finally { setLoading(false); } }; const fetchTaskLogs = async (taskId: number) => { if (!userId) return; // Initialize task logs state if not exists if (!taskLogs[taskId]) { setTaskLogs(prev => ({ ...prev, [taskId]: { logs: [], loading: false, error: null } })); } // Check if already loading if (taskLogs[taskId]?.loading) return; setTaskLogs(prev => ({ ...prev, [taskId]: { ...prev[taskId], loading: true, error: null } })); try { console.log(`[WebsiteAnalysis] Fetching logs for task ${taskId}...`); const response = await getWebsiteAnalysisLogs(userId, 10, 0, taskId); console.log(`[WebsiteAnalysis] Received logs response:`, { logsCount: response.logs?.length || 0, totalCount: response.total_count, hasLogs: !!(response.logs && response.logs.length > 0), firstLog: response.logs?.[0] || null }); if (response.logs && Array.isArray(response.logs)) { setTaskLogs(prev => ({ ...prev, [taskId]: { logs: response.logs, loading: false, error: null } })); } else { console.warn(`[WebsiteAnalysis] Invalid logs response structure:`, response); setTaskLogs(prev => ({ ...prev, [taskId]: { logs: [], loading: false, error: 'Invalid response structure' } })); } } catch (err: any) { console.error(`[WebsiteAnalysis] Error fetching logs for task ${taskId}:`, err); setTaskLogs(prev => ({ ...prev, [taskId]: { ...prev[taskId], loading: false, error: err.message || 'Failed to fetch logs' } })); } }; const handleRetry = async (taskId: number) => { if (!userId) return; try { setRefreshing(taskId); await retryWebsiteAnalysis(taskId); await fetchStatus(); // Refresh status } catch (err: any) { console.error('Error retrying website analysis:', err); alert(err.message || 'Failed to retry website analysis'); } finally { setRefreshing(null); } }; const handleToggleExpand = (taskId: number) => { if (expandedTaskId === taskId) { setExpandedTaskId(null); } else { setExpandedTaskId(taskId); // Always fetch logs when expanding to get latest data fetchTaskLogs(taskId); } }; useEffect(() => { fetchStatus(); // Refresh every 5 minutes (same as other dashboard components) // Tasks run on schedule (every 10 days for competitors, etc.), so frequent polling is unnecessary const interval = setInterval(fetchStatus, 5 * 60 * 1000); return () => clearInterval(interval); }, [userId]); // Fetch logs when task is expanded (similar to OAuth pattern) useEffect(() => { if (expandedTaskId && userId) { fetchTaskLogs(expandedTaskId); } }, [expandedTaskId, userId]); const getStatusIcon = (status: string) => { switch (status) { case 'active': return ; case 'failed': return ; case 'paused': return ; default: return ; } }; const getStatusChip = (taskStatus: string) => { switch (taskStatus) { case 'active': return ; case 'failed': return ; case 'paused': return ; default: return ; } }; const formatDate = (dateString: string | null) => { if (!dateString) return 'Never'; try { const date = new Date(dateString); return date.toLocaleString(); } catch { return dateString; } }; const getLogStatusChip = (logStatus: string) => { switch (logStatus) { case 'success': return ; case 'failed': return ; case 'running': return ; default: return ; } }; const formatLogResult = (resultData: any): string => { if (!resultData) return 'N/A'; if (typeof resultData === 'string') { try { resultData = JSON.parse(resultData); } catch { return resultData.substring(0, 50); } } if (resultData.style_analysis) { return 'Analysis completed'; } if (resultData.crawl_result) { return 'Crawl completed'; } const str = JSON.stringify(resultData); return str.length > 60 ? str.substring(0, 60) + '...' : str; }; if (loading && !status) { return ( ); } if (!status) { return null; } const allTasks = [ ...status.data.user_website_tasks, ...status.data.competitor_tasks ]; const renderTaskRow = (task: WebsiteAnalysisTask) => { const isExpanded = expandedTaskId === task.id; const logs = taskLogs[task.id]?.logs || []; const logsLoading = taskLogs[task.id]?.loading || false; const logsError = taskLogs[task.id]?.error; return ( handleToggleExpand(task.id)} > {task.task_type === 'user_website' ? ( ) : ( )} {task.website_url} {getStatusIcon(task.status)} {getStatusChip(task.status)} {task.last_success && ( )} {task.next_check && ( )} {task.status === 'failed' && ( )} { e.stopPropagation(); handleToggleExpand(task.id); }} sx={{ color: terminalColors.text }} > {isExpanded ? : } {task.failure_reason && ( Error: {task.failure_reason} )} Monitoring Logs {logsLoading ? ( ) : logsError ? ( {logsError} ) : logs.length === 0 ? ( No execution logs yet ) : ( Date Status Result Duration {logs.map((log) => ( setHoveredLogId(log.id)} onMouseLeave={() => setHoveredLogId(null)} > {formatDate(log.execution_date)} {getLogStatusChip(log.status)} {formatLogResult(log.result_data)} {log.execution_time_ms ? `${log.execution_time_ms}ms` : 'N/A'} {hoveredLogId === log.id && ( {log.error_message && ( Error: {log.error_message} )} {log.result_data && ( Result Data:
                                        {JSON.stringify(log.result_data, null, 2)}
                                      
)}
)}
))}
)}
); }; return ( Website Analysis Status {status && ( )} {status && status.data.failed_tasks > 0 && ( )} {error && ( {error} )} {status && ( <> {status.data.user_website_tasks.length > 0 && ( User Website ({status.data.user_website_tasks.length}) Website Status Timing Actions {status.data.user_website_tasks.map(renderTaskRow)}
)} {status.data.competitor_tasks.length > 0 && ( Competitors ({status.data.competitor_tasks.length}) Website Status Timing Actions {status.data.competitor_tasks.map(renderTaskRow)}
)} {allTasks.length === 0 && ( No website analysis tasks found. Complete onboarding to create tasks. )} )}
); }; export default WebsiteAnalysisStatus;