'use client'; import Link from 'next/link'; import { useState, useMemo } from 'react'; import { useJobs } from '@/contexts/JobsContext'; import { JobStatus } from '@/components/ScraperTest'; // Mock data for initial development - will be replaced with API data const MOCK_CLIENTS = [ { client_id: 'client-001', job_count: 45, success_rate: 94.2 }, { client_id: 'client-002', job_count: 38, success_rate: 89.5 }, { client_id: 'client-003', job_count: 27, success_rate: 96.3 }, { client_id: 'client-004', job_count: 19, success_rate: 84.2 }, { client_id: 'client-005', job_count: 12, success_rate: 91.7 }, ]; function formatDuration(seconds: number): string { if (seconds < 60) return `${seconds.toFixed(1)}s`; const mins = Math.floor(seconds / 60); const secs = Math.round(seconds % 60); return `${mins}m ${secs}s`; } function extractBusinessName(job: JobStatus): string { if (job.business_name) return job.business_name; try { const urlObj = new URL(job.url); const query = urlObj.searchParams.get('query'); return query ? decodeURIComponent(query) : 'Unknown Business'; } catch { return 'Unknown Business'; } } function formatDate(date: Date): string { return date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', }); } function getErrorType(errorMessage: string | null): string { if (!errorMessage) return 'Unknown Error'; const msg = errorMessage.toLowerCase(); if (msg.includes('timeout')) return 'Timeout'; if (msg.includes('network') || msg.includes('connection')) return 'Network Error'; if (msg.includes('captcha') || msg.includes('bot')) return 'Bot Detection'; if (msg.includes('element') || msg.includes('selector')) return 'Element Not Found'; if (msg.includes('memory')) return 'Memory Error'; return 'Scrape Error'; } export default function DashboardPage() { const { jobs, isLoading } = useJobs(); const [currentDate] = useState(new Date()); // Calculate stats from jobs data const stats = useMemo(() => { const now = new Date(); const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000); // Jobs from last 24 hours const recentJobs = jobs.filter( (j) => new Date(j.created_at) >= oneDayAgo ); // Currently running jobs const activeJobs = jobs.filter((j) => j.status === 'running'); // Completed jobs from last 24h const completedRecent = recentJobs.filter( (j) => j.status === 'completed' ); // Failed jobs from last 24h const failedRecent = recentJobs.filter( (j) => j.status === 'failed' ); // Calculate success rate const totalWithOutcome = completedRecent.length + failedRecent.length; const successRate = totalWithOutcome > 0 ? (completedRecent.length / totalWithOutcome) * 100 : 0; // Calculate average duration for completed jobs const completedWithTime = jobs.filter( (j) => j.status === 'completed' && j.scrape_time !== null ); const avgDuration = completedWithTime.length > 0 ? completedWithTime.reduce((sum, j) => sum + (j.scrape_time || 0), 0) / completedWithTime.length : 0; // Previous 24h for trend comparison const twoDaysAgo = new Date(now.getTime() - 48 * 60 * 60 * 1000); const previousDayJobs = jobs.filter( (j) => new Date(j.created_at) >= twoDaysAgo && new Date(j.created_at) < oneDayAgo ); const jobsTrend = recentJobs.length - previousDayJobs.length; return { totalJobs24h: recentJobs.length, jobsTrend, successRate: successRate.toFixed(1), activeJobs: activeJobs.length, avgDuration, }; }, [jobs]); // Jobs by status counts const statusCounts = useMemo(() => { return { pending: jobs.filter((j) => j.status === 'pending').length, running: jobs.filter((j) => j.status === 'running').length, completed: jobs.filter((j) => j.status === 'completed').length, failed: jobs.filter((j) => j.status === 'failed').length, partial: jobs.filter((j) => j.status === 'partial').length, }; }, [jobs]); // Recent failed jobs const recentFailedJobs = useMemo(() => { return jobs .filter((j) => j.status === 'failed' || j.status === 'partial') .sort( (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() ) .slice(0, 5); }, [jobs]); if (isLoading) { return (
); } return (
{/* Header */}

ReviewIQ Dashboard

{formatDate(currentDate)}

{/* System Health Cards */}
{/* Total Jobs (24h) */}
Total Jobs (24h)
{stats.totalJobs24h} {stats.jobsTrend !== 0 && ( 0 ? 'text-green-600' : 'text-red-600' }`} > {stats.jobsTrend > 0 ? ( ) : ( )} {Math.abs(stats.jobsTrend)} )}
{/* Success Rate (24h) */}
Success Rate (24h)
{stats.successRate}%
{/* Active Jobs */}
Active Jobs
{stats.activeJobs} {stats.activeJobs > 0 && (
Running )}
{/* Avg Duration */}
Avg Duration
{stats.avgDuration > 0 ? formatDuration(stats.avgDuration) : '--'}
{/* Jobs by Status */}

Jobs by Status

Pending {statusCounts.pending} Running {statusCounts.running} Completed {statusCounts.completed} Partial {statusCounts.partial} Failed {statusCounts.failed}
{/* Two Column Layout */}
{/* Recent Problems */}

Recent Problems

View all
{recentFailedJobs.length === 0 ? (

No recent failures

All systems running smoothly

) : (
{recentFailedJobs.map((job) => (
{getErrorType(job.error_message)} {new Date(job.created_at).toLocaleTimeString()}

{extractBusinessName(job)}

{job.url}

))}
)}
{/* Top Clients */}

Top Clients

View all
{MOCK_CLIENTS.map((client, index) => (
{index + 1}

{client.client_id}

{client.job_count} jobs

= 90 ? 'text-green-600' : client.success_rate >= 80 ? 'text-yellow-600' : 'text-red-600' }`} > {client.success_rate}%

success

))}
{/* Quick Actions */}
New Scrape View All Jobs Analytics
); }