import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Box, Button, Card, CardContent, Typography, LinearProgress, Alert, Chip, List, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, CircularProgress, Accordion, AccordionSummary, AccordionDetails, } from '@mui/material'; import { PlayArrow, Stop, Refresh, CheckCircle, Error as ErrorIcon, Schedule, ExpandMore, Analytics, DataUsage, } from '@mui/icons-material'; import { apiClient } from '../../api/client'; interface Job { job_id: string; job_type: string; user_id: string; status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'; progress: number; message: string; created_at: string; started_at?: string; completed_at?: string; result?: any; error?: string; } interface BackgroundJobManagerProps { siteUrl?: string; days?: number; onJobCompleted?: (job: Job) => void; } const BackgroundJobManager: React.FC = ({ siteUrl = 'https://www.alwrity.com/', days = 30, onJobCompleted, }) => { const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(false); const [selectedJob, setSelectedJob] = useState(null); const [jobDialogOpen, setJobDialogOpen] = useState(false); const [hasRunningJobs, setHasRunningJobs] = useState(false); // Fetch user jobs const fetchJobs = useCallback(async () => { try { const response = await apiClient.get('/api/background-jobs/user-jobs?limit=10'); if (response.data.success) { const newJobs = response.data.data.jobs || []; setJobs(newJobs); // Update running jobs state const runningJobs = newJobs.some((job: Job) => job.status === 'running' || job.status === 'pending'); setHasRunningJobs(runningJobs); } } catch (error) { console.error('Error fetching jobs:', error); } }, []); // Create Bing comprehensive insights job const createComprehensiveInsightsJob = async () => { setLoading(true); try { const response = await apiClient.post( `/api/background-jobs/bing/comprehensive-insights?site_url=${encodeURIComponent(siteUrl)}&days=${days}` ); if (response.data.success) { const jobId = response.data.data.job_id; console.log('✅ Comprehensive insights job created:', jobId); // Refresh jobs list await fetchJobs(); // Show success message alert(`Background job created successfully! Job ID: ${jobId}\n\nThis will generate comprehensive Bing insights in the background. Check the job status below for progress.`); } } catch (error) { console.error('Error creating comprehensive insights job:', error); alert('Failed to create background job. Please try again.'); } finally { setLoading(false); } }; // Create Bing data collection job const createDataCollectionJob = async () => { setLoading(true); try { const response = await apiClient.post( `/api/background-jobs/bing/data-collection?site_url=${encodeURIComponent(siteUrl)}&days_back=${days}` ); if (response.data.success) { const jobId = response.data.data.job_id; console.log('✅ Data collection job created:', jobId); // Refresh jobs list await fetchJobs(); alert(`Background data collection job created successfully! Job ID: ${jobId}\n\nThis will collect fresh data from Bing API in the background.`); } } catch (error) { console.error('Error creating data collection job:', error); alert('Failed to create data collection job. Please try again.'); } finally { setLoading(false); } }; // Cancel job const cancelJob = async (jobId: string) => { try { const response = await apiClient.post(`/api/background-jobs/cancel/${jobId}`); if (response.data.success) { console.log('✅ Job cancelled:', jobId); await fetchJobs(); alert('Job cancelled successfully'); } else { alert(response.data.message || 'Failed to cancel job'); } } catch (error) { console.error('Error cancelling job:', error); alert('Failed to cancel job. Please try again.'); } }; // View job details const viewJobDetails = async (jobId: string) => { try { const response = await apiClient.get(`/api/background-jobs/status/${jobId}`); if (response.data.success) { setSelectedJob(response.data.data); setJobDialogOpen(true); // Call onJobCompleted if job is completed if (response.data.data.status === 'completed' && onJobCompleted) { onJobCompleted(response.data.data); } } } catch (error) { console.error('Error fetching job details:', error); alert('Failed to fetch job details'); } }; // Get status color const getStatusColor = (status: string) => { switch (status) { case 'completed': return 'success'; case 'failed': return 'error'; case 'running': return 'primary'; case 'pending': return 'warning'; case 'cancelled': return 'default'; default: return 'default'; } }; // Get status icon const getStatusIcon = (status: string) => { switch (status) { case 'completed': return ; case 'failed': return ; case 'running': return ; case 'pending': return ; case 'cancelled': return ; default: return ; } }; // Format job type const formatJobType = (jobType: string) => { switch (jobType) { case 'bing_comprehensive_insights': return 'Bing Comprehensive Insights'; case 'bing_data_collection': return 'Bing Data Collection'; case 'analytics_refresh': return 'Analytics Refresh'; default: return jobType; } }; // One-run guard to prevent duplicate calls in StrictMode const jobsFetchedRef = useRef(false); // Poll for job updates useEffect(() => { if (jobsFetchedRef.current) return; jobsFetchedRef.current = true; fetchJobs(); // Only start polling if there are running jobs let interval: NodeJS.Timeout | null = null; if (hasRunningJobs) { interval = setInterval(() => { fetchJobs().catch(console.error); }, 5000); } return () => { if (interval) { clearInterval(interval); } }; }, [fetchJobs, hasRunningJobs]); // Only depend on hasRunningJobs state return ( {/* Action Buttons */} Background Job Actions Run expensive operations in the background to avoid timeouts and improve user experience. {/* Jobs List */} Background Jobs {jobs.length === 0 ? ( No background jobs found. Create a job using the buttons above. ) : ( {jobs.map((job) => ( }> {getStatusIcon(job.status)} {formatJobType(job.job_type)} {new Date(job.created_at).toLocaleString()} {/* Progress Bar */} {(job.status === 'running' || job.status === 'pending') && ( Progress: {job.progress}% )} {/* Job Message */} Status: {job.message} {/* Job Details */} Job ID: {job.job_id} {job.started_at && ( Started: {new Date(job.started_at).toLocaleString()} )} {job.completed_at && ( Completed: {new Date(job.completed_at).toLocaleString()} )} {job.error && ( Error: {job.error} )} {/* Action Buttons */} {(job.status === 'pending' || job.status === 'running') && ( )} ))} )} {/* Job Details Dialog */} setJobDialogOpen(false)} maxWidth="md" fullWidth > Job Details - {selectedJob?.job_id} {selectedJob && ( Type: {formatJobType(selectedJob.job_type)} Status: {selectedJob.status.toUpperCase()} Message: {selectedJob.message} Progress: {selectedJob.progress}% Created: {new Date(selectedJob.created_at).toLocaleString()} {selectedJob.started_at && ( Started: {new Date(selectedJob.started_at).toLocaleString()} )} {selectedJob.completed_at && ( Completed: {new Date(selectedJob.completed_at).toLocaleString()} )} {selectedJob.result && ( Results:
                    {JSON.stringify(selectedJob.result, null, 2)}
                  
)} {selectedJob.error && ( Error: {selectedJob.error} )}
)}
); }; export default BackgroundJobManager;