'use client'; import ScraperTest, { JobStatus } from '@/components/ScraperTest'; import ReviewAnalytics from '@/components/ReviewAnalytics'; import Sidebar from '@/components/Sidebar'; import JobsView from '@/components/JobsView'; import { useState, useCallback, useEffect } from 'react'; interface Review { author: string; rating: number; text: string | null; date_text: string; avatar_url: string | null; profile_url: string | null; review_id: string; } interface ReviewWithNew extends Review { is_new?: boolean; } interface SelectedJob { reviews: ReviewWithNew[]; businessName: string; businessUrl: string; jobId: string; newCount?: number; previousJobId?: string; businessCategory?: string; reviewTopics?: { topic: string; count: number }[]; } type ViewType = 'newScrape' | 'jobs' | 'reports'; export default function Home() { const [activeView, setActiveView] = useState('newScrape'); const [jobs, setJobs] = useState([]); const [selectedJob, setSelectedJob] = useState(null); const [isLoadingJob, setIsLoadingJob] = useState(null); // Load jobs from API const refreshJobs = useCallback(async () => { try { const response = await fetch('/api/jobs?limit=100'); if (response.ok) { const data = await response.json(); if (data.jobs) { setJobs(data.jobs); } } } catch (err) { console.error('Failed to load jobs:', err); } }, []); // Load jobs from API on mount useEffect(() => { refreshJobs(); }, [refreshJobs]); const handleJobsChange = useCallback((newJobs: JobStatus[]) => { setJobs(prev => { // Merge new jobs with existing, updating duplicates const jobMap = new Map(prev.map(j => [j.job_id, j])); newJobs.forEach(job => jobMap.set(job.job_id, job)); return Array.from(jobMap.values()); }); }, []); const handleSelectReviews = useCallback((reviews: Review[], businessName: string, jobId: string, businessUrl?: string) => { setSelectedJob({ reviews, businessName, businessUrl: businessUrl || '', jobId }); setActiveView('reports'); }, []); const loadJobReviews = async (job: JobStatus, previousJob?: JobStatus) => { if (job.status !== 'completed' || !job.reviews_count) return; setIsLoadingJob(job.job_id); try { // Use compare API if we have a previous job const url = previousJob ? `/api/jobs/${job.job_id}/compare?previous=${previousJob.job_id}` : `/api/jobs/${job.job_id}/reviews?limit=10000`; const response = await fetch(url); if (!response.ok) throw new Error('Failed to fetch reviews'); const data = await response.json(); const reviews = data.reviews || []; if (reviews.length > 0) { // Extract business name from URL query param as fallback let businessName = job.business_name; if (!businessName) { try { const urlObj = new URL(job.url); const query = urlObj.searchParams.get('query'); businessName = query ? decodeURIComponent(query) : 'Unknown Business'; } catch { businessName = 'Unknown Business'; } } setSelectedJob({ reviews, businessName, businessUrl: job.url, jobId: job.job_id, newCount: data.new_count, previousJobId: previousJob?.job_id, businessCategory: job.business_category || undefined, reviewTopics: job.review_topics || undefined, }); setActiveView('reports'); } } catch (err) { console.error('Failed to load job reviews:', err); } finally { setIsLoadingJob(null); } }; const renderMainContent = () => { switch (activeView) { case 'newScrape': return (
); case 'jobs': return ( ); case 'reports': { // Get completed jobs with reviews const completedJobs = jobs .filter(j => j.status === 'completed' && j.reviews_count && j.reviews_count > 0) .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); return selectedJob ? (

Analytics

) : (

Reports

{completedJobs.length} completed {completedJobs.length === 1 ? 'scrape' : 'scrapes'} with reviews

{completedJobs.length === 0 ? (

No Reports Yet

Complete a scrape job to see analytics reports

) : (
{completedJobs.map(job => { // Extract business name from URL as fallback let businessName = job.business_name; if (!businessName) { try { const urlObj = new URL(job.url); const query = urlObj.searchParams.get('query'); businessName = query ? decodeURIComponent(query) : 'Unknown Business'; } catch { businessName = 'Unknown Business'; } } return (
loadJobReviews(job)} className="bg-white rounded-xl border-2 border-gray-200 p-5 cursor-pointer hover:border-blue-400 hover:shadow-lg transition-all" >

{businessName}

{job.rating_snapshot && ( {job.rating_snapshot.toFixed(1)} )}
{job.reviews_count} reviews {job.scrape_time && {job.scrape_time.toFixed(1)}s}
{new Date(job.created_at).toLocaleDateString()} at {new Date(job.created_at).toLocaleTimeString()}
{isLoadingJob === job.job_id && (
Loading...
)}
); })}
)}
); } } }; return (
{/* Sidebar */} {/* Main Content */}
{renderMainContent()}
); }