/** * ComparisonView Component * Client-side comparison interface with charts and tables */ 'use client'; import { useEffect, useState } from 'react'; import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { useComparison } from '@/hooks/useComparison'; import { ComparisonChart } from './ComparisonChart'; import { SchoolSearchModal } from './SchoolSearchModal'; import { EmptyState } from './EmptyState'; import type { ComparisonData, MetricDefinition } from '@/lib/types'; import { formatPercentage, formatProgress } from '@/lib/utils'; import styles from './ComparisonView.module.css'; interface ComparisonViewProps { initialData: Record | null; initialUrns: number[]; metrics: MetricDefinition[]; selectedMetric: string; } export function ComparisonView({ initialData, initialUrns, metrics, selectedMetric: initialMetric, }: ComparisonViewProps) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const { selectedSchools, removeSchool } = useComparison(); const [selectedMetric, setSelectedMetric] = useState(initialMetric); const [isModalOpen, setIsModalOpen] = useState(false); const [comparisonData, setComparisonData] = useState(initialData); // Sync URL with selected schools useEffect(() => { const urns = selectedSchools.map((s) => s.urn).join(','); const params = new URLSearchParams(searchParams); if (urns) { params.set('urns', urns); } else { params.delete('urns'); } params.set('metric', selectedMetric); const newUrl = `${pathname}?${params.toString()}`; router.replace(newUrl, { scroll: false }); // Fetch comparison data if (selectedSchools.length > 0) { const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || '/api'; console.log('Fetching comparison data for URNs:', urns); fetch(`${apiBaseUrl}/compare?urns=${urns}`) .then((res) => { console.log('Comparison API response status:', res.status); return res.json(); }) .then((data) => { console.log('Comparison data received:', data); setComparisonData(data.comparison); }) .catch((err) => { console.error('Failed to fetch comparison:', err); setComparisonData(null); }); } else { setComparisonData(null); } }, [selectedSchools, selectedMetric, pathname, searchParams, router]); const handleMetricChange = (metric: string) => { setSelectedMetric(metric); }; const handleRemoveSchool = (urn: number) => { removeSchool(urn); }; // Get metric definition const currentMetricDef = metrics.find((m) => m.key === selectedMetric); const metricLabel = currentMetricDef?.label || selectedMetric; // No schools selected if (selectedSchools.length === 0) { return (

Compare Schools

Add schools to your comparison basket to see side-by-side performance data

router.push('/'), }} />
); } // Get years for table const years = comparisonData && Object.keys(comparisonData).length > 0 ? comparisonData[Object.keys(comparisonData)[0]].yearly_data.map((d) => d.year) : []; return (
{/* Header */}

Compare Schools

Comparing {selectedSchools.length} school{selectedSchools.length !== 1 ? 's' : ''}

{/* Metric Selector */}
{/* School Cards */}
{selectedSchools.map((school) => (

{school.school_name}

{school.local_authority && ( {school.local_authority} )} {school.school_type && ( {school.school_type} )}
{/* Latest metric value */} {comparisonData && comparisonData[school.urn] && (
{metricLabel}
{(() => { const yearlyData = comparisonData[school.urn].yearly_data; if (yearlyData.length === 0) return '-'; const latestData = yearlyData[yearlyData.length - 1]; const value = latestData[selectedMetric as keyof typeof latestData]; if (value === null || value === undefined) return '-'; // Format based on metric type if (selectedMetric.includes('progress')) { return formatProgress(value as number); } else if (selectedMetric.includes('pct') || selectedMetric.includes('rate')) { return formatPercentage(value as number); } else { return typeof value === 'number' ? value.toFixed(1) : String(value); } })()}
)}
))}
{/* Comparison Chart */} {comparisonData && Object.keys(comparisonData).length > 0 ? (

Performance Over Time

) : selectedSchools.length > 0 ? (
Loading comparison data...
) : null} {/* Comparison Table */} {comparisonData && Object.keys(comparisonData).length > 0 && years.length > 0 && (

Detailed Comparison

{selectedSchools.map((school) => ( ))} {years.map((year) => ( {selectedSchools.map((school) => { const schoolData = comparisonData[school.urn]; if (!schoolData) return ; const yearData = schoolData.yearly_data.find((d) => d.year === year); if (!yearData) return ; const value = yearData[selectedMetric as keyof typeof yearData]; if (value === null || value === undefined) { return ; } // Format based on metric type let displayValue: string; if (selectedMetric.includes('progress')) { displayValue = formatProgress(value as number); } else if (selectedMetric.includes('pct') || selectedMetric.includes('rate')) { displayValue = formatPercentage(value as number); } else { displayValue = typeof value === 'number' ? value.toFixed(1) : String(value); } return ; })} ))}
Year{school.school_name}
{year}---{displayValue}
)} {/* School Search Modal */} setIsModalOpen(false)} />
); }