/** * HomeView Component * Client-side home page view with search and filtering */ 'use client'; import { useState, useEffect, useRef } from 'react'; import { useSearchParams } from 'next/navigation'; import { FilterBar } from './FilterBar'; import { SchoolRow } from './SchoolRow'; import { SecondarySchoolRow } from './SecondarySchoolRow'; import { SchoolMap } from './SchoolMap'; import { EmptyState } from './EmptyState'; import { useComparisonContext } from '@/context/ComparisonContext'; import { fetchSchools, fetchLAaverages } from '@/lib/api'; import type { SchoolsResponse, Filters, School } from '@/lib/types'; import { schoolUrl } from '@/lib/utils'; import styles from './HomeView.module.css'; interface HomeViewProps { initialSchools: SchoolsResponse; filters: Filters; totalSchools?: number | null; } export function HomeView({ initialSchools, filters, totalSchools }: HomeViewProps) { const searchParams = useSearchParams(); const { addSchool, removeSchool, selectedSchools } = useComparisonContext(); const [resultsView, setResultsView] = useState<'list' | 'map'>('list'); const [selectedMapSchool, setSelectedMapSchool] = useState(null); const [sortOrder, setSortOrder] = useState('default'); const [allSchools, setAllSchools] = useState(initialSchools.schools); const [currentPage, setCurrentPage] = useState(initialSchools.page); const [hasMore, setHasMore] = useState(initialSchools.total_pages > 1); const [isLoadingMore, setIsLoadingMore] = useState(false); const [laAverages, setLaAverages] = useState>({}); const prevSearchParamsRef = useRef(searchParams.toString()); const hasSearch = searchParams.get('search') || searchParams.get('postcode'); const isLocationSearch = !!searchParams.get('postcode'); const isSearchActive = !!(hasSearch || searchParams.get('local_authority') || searchParams.get('school_type')); const currentPhase = searchParams.get('phase') || ''; const hasSecondaryResults = allSchools.some(s => s.attainment_8_score != null); const isSecondaryView = currentPhase.toLowerCase().includes('secondary') || hasSecondaryResults; // Reset pagination state when search params change useEffect(() => { const newParamsStr = searchParams.toString(); if (newParamsStr !== prevSearchParamsRef.current) { prevSearchParamsRef.current = newParamsStr; setAllSchools(initialSchools.schools); setCurrentPage(initialSchools.page); setHasMore(initialSchools.total_pages > 1); } }, [searchParams, initialSchools]); // Close bottom sheet if we change views or search useEffect(() => { setSelectedMapSchool(null); }, [resultsView, searchParams]); // Fetch LA averages when secondary schools are visible useEffect(() => { if (!isSecondaryView) return; fetchLAaverages({ cache: 'force-cache' }) .then(data => setLaAverages(data.secondary.attainment_8_by_la)) .catch(() => {}); }, [isSecondaryView]); const handleLoadMore = async () => { if (isLoadingMore || !hasMore) return; setIsLoadingMore(true); try { const params: Record = {}; searchParams.forEach((value, key) => { params[key] = value; }); params.page = currentPage + 1; const response = await fetchSchools(params, { cache: 'no-store' }); setAllSchools(prev => [...prev, ...response.schools]); setCurrentPage(response.page); setHasMore(response.page < response.total_pages); } catch { // silently ignore } finally { setIsLoadingMore(false); } }; const sortedSchools = [...allSchools].sort((a, b) => { if (sortOrder === 'rwm_desc') return (b.rwm_expected_pct ?? -Infinity) - (a.rwm_expected_pct ?? -Infinity); if (sortOrder === 'rwm_asc') return (a.rwm_expected_pct ?? Infinity) - (b.rwm_expected_pct ?? Infinity); if (sortOrder === 'att8_desc') return (b.attainment_8_score ?? -Infinity) - (a.attainment_8_score ?? -Infinity); if (sortOrder === 'att8_asc') return (a.attainment_8_score ?? Infinity) - (b.attainment_8_score ?? Infinity); if (sortOrder === 'distance') return (a.distance ?? Infinity) - (b.distance ?? Infinity); if (sortOrder === 'name_asc') return a.school_name.localeCompare(b.school_name); return 0; }); return (
{/* Combined Hero + Search and Filters */} {!isSearchActive && (

Find Local Schools

Compare school results (SATs and GCSE), for thousands of schools across England

)} {/* Discovery section shown on landing page before any search */} {!isSearchActive && initialSchools.schools.length === 0 && (
{totalSchools &&

{totalSchools.toLocaleString()}+ primary and secondary schools across England

}

Try searching for a school name, or enter a postcode to find schools near you.

Quick searches: {['Manchester', 'Bristol', 'Leeds', 'Birmingham'].map(city => ( {city} ))}
)} {/* Location Info Banner with View Toggle */} {isLocationSearch && initialSchools.location_info && (
Showing schools within {(initialSchools.location_info.radius / 1.60934).toFixed(1)} miles of{' '} {initialSchools.location_info.postcode}
{initialSchools.schools.length > 0 && (
)}
)} {/* Results Section */}
{!hasSearch && initialSchools.schools.length > 0 && (

Featured Schools

Explore schools from across England

)} {hasSearch && resultsView === 'list' && (

{initialSchools.total.toLocaleString()} school {initialSchools.total !== 1 ? 's' : ''} found

)} {isSearchActive && (
{searchParams.get('search') && Search: {searchParams.get('search')} { e.preventDefault(); }}>×} {searchParams.get('local_authority') && {searchParams.get('local_authority')}} {searchParams.get('school_type') && {searchParams.get('school_type')}} {searchParams.get('postcode') && Near {searchParams.get('postcode')} ({parseFloat(searchParams.get('radius') || '1')} mi)}
)} {initialSchools.schools.length === 0 && isSearchActive ? ( { window.location.href = '/'; }, }} /> ) : initialSchools.schools.length > 0 && resultsView === 'map' && isLocationSearch ? ( /* Map View Layout */
{initialSchools.schools.map((school) => (
s.urn === school.urn)} />
))}
{/* Mobile Bottom Sheet for Selected Map Pin */} {selectedMapSchool && (
s.urn === selectedMapSchool.urn)} />
)}
) : ( /* List View Layout */ <>
{sortedSchools.map((school) => ( school.attainment_8_score != null ? ( s.urn === school.urn)} laAvgAttainment8={school.local_authority ? laAverages[school.local_authority] ?? null : null} /> ) : ( s.urn === school.urn)} /> ) ))}
{(hasMore || allSchools.length < initialSchools.total) && (

Showing {allSchools.length.toLocaleString()} of {initialSchools.total.toLocaleString()} schools

{hasMore && ( )}
)} )}
); } /* Compact School Item for Map View */ interface CompactSchoolItemProps { school: School; onAddToCompare: (school: School) => void; isInCompare: boolean; } function CompactSchoolItem({ school, onAddToCompare, isInCompare }: CompactSchoolItemProps) { return (
{school.school_name} {school.distance !== undefined && school.distance !== null && ( {school.distance.toFixed(1)} mi )}
{school.school_type && {school.school_type}} {school.local_authority && {school.local_authority}}
{school.attainment_8_score != null ? school.attainment_8_score.toFixed(1) : school.rwm_expected_pct !== null ? `${school.rwm_expected_pct}%` : '-'} {' '} {school.attainment_8_score != null ? 'Att 8' : 'RWM'} {school.total_pupils || '-'} pupils
View
); }