/** * SchoolDetailView Component * Displays comprehensive school information with performance charts */ 'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useComparison } from '@/hooks/useComparison'; import { PerformanceChart } from './PerformanceChart'; import { SchoolMap } from './SchoolMap'; import { MetricTooltip } from './MetricTooltip'; import type { School, SchoolResult, AbsenceData, OfstedInspection, OfstedParentView, SchoolCensus, SchoolAdmissions, SenDetail, Phonics, SchoolDeprivation, SchoolFinance, NationalAverages, } from '@/lib/types'; import { formatPercentage, formatProgress, formatAcademicYear } from '@/lib/utils'; import styles from './SchoolDetailView.module.css'; const OFSTED_LABELS: Record = { 1: 'Outstanding', 2: 'Good', 3: 'Requires Improvement', 4: 'Inadequate', }; const RC_LABELS: Record = { 1: 'Exceptional', 2: 'Strong', 3: 'Expected standard', 4: 'Needs attention', 5: 'Urgent improvement', }; const RC_CATEGORIES = [ { key: 'rc_inclusion' as const, label: 'Inclusion' }, { key: 'rc_curriculum_teaching' as const, label: 'Curriculum & Teaching' }, { key: 'rc_achievement' as const, label: 'Achievement' }, { key: 'rc_attendance_behaviour' as const, label: 'Attendance & Behaviour' }, { key: 'rc_personal_development' as const, label: 'Personal Development' }, { key: 'rc_leadership_governance' as const, label: 'Leadership & Governance' }, { key: 'rc_early_years' as const, label: 'Early Years' }, { key: 'rc_sixth_form' as const, label: 'Sixth Form' }, ]; function progressClass(val: number | null | undefined): string { if (val == null) return ''; if (val > 0) return styles.progressPositive; if (val < 0) return styles.progressNegative; return ''; } interface SchoolDetailViewProps { schoolInfo: School; yearlyData: SchoolResult[]; absenceData: AbsenceData | null; ofsted: OfstedInspection | null; parentView: OfstedParentView | null; census: SchoolCensus | null; admissions: SchoolAdmissions | null; senDetail: SenDetail | null; phonics: Phonics | null; deprivation: SchoolDeprivation | null; finance: SchoolFinance | null; } export function SchoolDetailView({ schoolInfo, yearlyData, absenceData, ofsted, parentView, census, admissions, senDetail, phonics, deprivation, finance, }: SchoolDetailViewProps) { const router = useRouter(); const { addSchool, removeSchool, isSelected } = useComparison(); const isInComparison = isSelected(schoolInfo.urn); const latestResults = yearlyData.length > 0 ? yearlyData[yearlyData.length - 1] : null; // Phase detection const phase = schoolInfo.phase ?? ''; const isSecondary = phase.toLowerCase().includes('secondary') || phase.toLowerCase() === 'all-through'; const isPrimary = !isSecondary; // National averages (fetched dynamically so they stay current) const [nationalAvg, setNationalAvg] = useState(null); useEffect(() => { fetch('/api/national-averages') .then(r => r.ok ? r.json() : null) .then(data => { if (data) setNationalAvg(data); }) .catch(() => {}); }, []); const primaryAvg = nationalAvg?.primary ?? {}; const secondaryAvg = nationalAvg?.secondary ?? {}; const handleComparisonToggle = () => { if (isInComparison) { removeSchool(schoolInfo.urn); } else { addSchool(schoolInfo); } }; const deprivationDesc = (decile: number) => { if (decile <= 3) return `This school is in one of England's most deprived areas (decile ${decile}/10). Many pupils may face additional challenges at home.`; if (decile <= 7) return `This school is in an area with average levels of deprivation (decile ${decile}/10).`; return `This school is in one of England's less deprived areas (decile ${decile}/10).`; }; // Guard for Pupils & Inclusion — only show if at least one metric is available const hasInclusionData = (latestResults?.disadvantaged_pct != null) || (latestResults?.eal_pct != null) || (latestResults?.sen_support_pct != null) || senDetail != null; const hasSchoolLife = absenceData != null || census?.class_size_avg != null; const hasPhonics = phonics != null && phonics.year1_phonics_pct != null; const hasDeprivation = deprivation != null && deprivation.idaci_decile != null; const hasFinance = finance != null && finance.per_pupil_spend != null; const hasLocation = schoolInfo.latitude != null && schoolInfo.longitude != null; // Determine whether this school has KS2 or KS4 results to show const hasKS2Results = latestResults != null && latestResults.rwm_expected_pct != null; const hasKS4Results = latestResults != null && latestResults.attainment_8_score != null; const hasAnyResults = hasKS2Results || hasKS4Results; // Build section nav items dynamically — only sections with data const navItems: { id: string; label: string }[] = []; if (ofsted) navItems.push({ id: 'ofsted', label: 'Ofsted' }); if (parentView && parentView.total_responses != null && parentView.total_responses > 0) navItems.push({ id: 'parents', label: 'Parents' }); if (hasAnyResults) navItems.push({ id: 'results', label: isSecondary ? 'GCSEs' : 'SATs' }); if (hasPhonics && isPrimary) navItems.push({ id: 'phonics', label: 'Phonics' }); if (hasSchoolLife) navItems.push({ id: 'school-life', label: 'School Life' }); if (admissions) navItems.push({ id: 'admissions', label: 'Admissions' }); if (hasInclusionData) navItems.push({ id: 'inclusion', label: 'Pupils' }); if (hasLocation) navItems.push({ id: 'location', label: 'Location' }); if (hasDeprivation) navItems.push({ id: 'local-area', label: 'Local Area' }); if (hasFinance) navItems.push({ id: 'finances', label: 'Finances' }); if (yearlyData.length > 0) navItems.push({ id: 'history', label: 'History' }); return (
{/* Header */}

{schoolInfo.school_name}

{schoolInfo.local_authority && ( {schoolInfo.local_authority} )} {schoolInfo.school_type && ( {schoolInfo.school_type} )} {schoolInfo.gender && schoolInfo.gender !== 'Mixed' && ( {schoolInfo.gender}'s school )}
{schoolInfo.address && (

{schoolInfo.address}{schoolInfo.postcode && `, ${schoolInfo.postcode}`}

)}
{schoolInfo.headteacher_name && ( Headteacher: {schoolInfo.headteacher_name} )} {schoolInfo.website && ( School website ↗ )} {latestResults?.total_pupils != null && ( Pupils: {latestResults.total_pupils.toLocaleString()} {schoolInfo.capacity != null && ` (capacity: ${schoolInfo.capacity})`} )} {schoolInfo.trust_name && ( Part of {schoolInfo.trust_name} )}
{/* Sticky Section Navigation */} {/* Ofsted Rating / Report Card */} {ofsted && (

{ofsted.framework === 'ReportCard' ? 'Ofsted Report Card' : 'Ofsted Rating'} {ofsted.inspection_date && ( Inspected {new Date(ofsted.inspection_date).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })} )} Full report ↗

{ofsted.framework === 'ReportCard' ? ( /* ── New Report Card layout ── */ <>

From November 2025, Ofsted replaced single overall grades with Report Cards rating schools across several areas.

{ofsted.rc_safeguarding_met != null && (
Safeguarding
{ofsted.rc_safeguarding_met ? 'Met' : 'Not met'}
)} {RC_CATEGORIES.map(({ key, label }) => { const value = ofsted[key] as number | null; return value != null ? (
{label}
{RC_LABELS[value]}
) : null; })}
{parentView?.q_recommend_pct != null && parentView.total_responses != null && parentView.total_responses > 0 && (

{Math.round(parentView.q_recommend_pct)}% of parents would recommend this school ({parentView.total_responses.toLocaleString()} responses)

)} ) : ( /* ── Old OEIF layout ── */ <>
{ofsted.overall_effectiveness ? OFSTED_LABELS[ofsted.overall_effectiveness] : 'Not rated'} {ofsted.previous_overall != null && ofsted.previous_overall !== ofsted.overall_effectiveness && ( Previously: {OFSTED_LABELS[ofsted.previous_overall]} )}

From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections of state-funded schools.

{parentView?.q_recommend_pct != null && parentView.total_responses != null && parentView.total_responses > 0 && (

{Math.round(parentView.q_recommend_pct)}% of parents would recommend this school ({parentView.total_responses.toLocaleString()} responses)

)}
{[ { label: 'Quality of Teaching', value: ofsted.quality_of_education }, { label: 'Behaviour in School', value: ofsted.behaviour_attitudes }, { label: 'Pupils\' Wider Development', value: ofsted.personal_development }, { label: 'School Leadership', value: ofsted.leadership_management }, ...(ofsted.early_years_provision != null ? [{ label: 'Early Years (Reception)', value: ofsted.early_years_provision }] : []), ].map(({ label, value }) => value != null && (
{label}
{OFSTED_LABELS[value]}
))}
)}
)} {/* What Parents Say */} {parentView && parentView.total_responses != null && parentView.total_responses > 0 && (

What Parents Say {parentView.total_responses.toLocaleString()} responses

From the Ofsted Parent View survey — parents share their experience of this school.

{[ { label: 'Would recommend this school', pct: parentView.q_recommend_pct }, { label: 'My child is happy here', pct: parentView.q_happy_pct }, { label: 'My child feels safe here', pct: parentView.q_safe_pct }, { label: 'Teaching is good', pct: parentView.q_teaching_pct }, { label: 'My child makes good progress', pct: parentView.q_progress_pct }, { label: 'School looks after pupils\' wellbeing', pct: parentView.q_wellbeing_pct }, { label: 'Behaviour is well managed', pct: parentView.q_behaviour_pct }, { label: 'School deals well with bullying', pct: parentView.q_bullying_pct }, { label: 'Communicates well with parents', pct: parentView.q_communication_pct }, ].filter(q => q.pct != null).map(({ label, pct }) => (
{label}
{Math.round(pct!)}%
))}
)} {/* Results Section (SATs for primary, GCSEs for secondary) */} {hasAnyResults && latestResults && (

{isSecondary ? 'GCSE Results' : 'SATs Results'} ({formatAcademicYear(latestResults.year)})

{isSecondary ? 'GCSE results for Year 11 pupils. National averages shown for comparison.' : 'End-of-primary-school tests taken by Year 6 pupils. National averages shown for comparison.'}

{/* ── Primary / KS2 content ── */} {hasKS2Results && ( <>
{latestResults.rwm_expected_pct !== null && (
Reading, Writing & Maths combined
{formatPercentage(latestResults.rwm_expected_pct)}
{primaryAvg.rwm_expected_pct != null && (
National avg: {primaryAvg.rwm_expected_pct.toFixed(0)}%
)}
)} {latestResults.rwm_high_pct !== null && (
Exceeding expected level (Reading, Writing & Maths)
{formatPercentage(latestResults.rwm_high_pct)}
{primaryAvg.rwm_high_pct != null && (
National avg: {primaryAvg.rwm_high_pct.toFixed(0)}%
)}
)}

Reading

{latestResults.reading_expected_pct !== null && (
Expected level {formatPercentage(latestResults.reading_expected_pct)}
)} {latestResults.reading_high_pct !== null && (
Exceeding {formatPercentage(latestResults.reading_high_pct)}
)} {latestResults.reading_progress !== null && (
Progress score {formatProgress(latestResults.reading_progress)}
)} {latestResults.reading_avg_score !== null && (
Average score {latestResults.reading_avg_score.toFixed(1)}
)}

Writing

{latestResults.writing_expected_pct !== null && (
Expected level {formatPercentage(latestResults.writing_expected_pct)}
)} {latestResults.writing_high_pct !== null && (
Exceeding {formatPercentage(latestResults.writing_high_pct)}
)} {latestResults.writing_progress !== null && (
Progress score {formatProgress(latestResults.writing_progress)}
)}

Maths

{latestResults.maths_expected_pct !== null && (
Expected level {formatPercentage(latestResults.maths_expected_pct)}
)} {latestResults.maths_high_pct !== null && (
Exceeding {formatPercentage(latestResults.maths_high_pct)}
)} {latestResults.maths_progress !== null && (
Progress score {formatProgress(latestResults.maths_progress)}
)} {latestResults.maths_avg_score !== null && (
Average score {latestResults.maths_avg_score.toFixed(1)}
)}
{(latestResults.reading_progress !== null || latestResults.writing_progress !== null || latestResults.maths_progress !== null) && (

Progress scores measure how much pupils improved compared to similar schools nationally. Above 0 = better than average, below 0 = below average.

)} )} {/* ── Secondary / KS4 content ── */} {hasKS4Results && ( <>
{latestResults.attainment_8_score !== null && (
Attainment 8
{latestResults.attainment_8_score.toFixed(1)}
{secondaryAvg.attainment_8_score != null && (
National avg: {secondaryAvg.attainment_8_score.toFixed(1)}
)}
)} {latestResults.progress_8_score !== null && (
Progress 8
{formatProgress(latestResults.progress_8_score)}
0 = national average
)} {latestResults.english_maths_standard_pass_pct !== null && (
English & Maths Grade 4+
{formatPercentage(latestResults.english_maths_standard_pass_pct)}
{secondaryAvg.english_maths_standard_pass_pct != null && (
National avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%
)}
)} {latestResults.english_maths_strong_pass_pct !== null && (
English & Maths Grade 5+
{formatPercentage(latestResults.english_maths_strong_pass_pct)}
{secondaryAvg.english_maths_strong_pass_pct != null && (
National avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%
)}
)}
{/* EBacc */} {(latestResults.ebacc_entry_pct !== null || latestResults.ebacc_standard_pass_pct !== null) && ( <>

English Baccalaureate (EBacc)

{latestResults.ebacc_entry_pct !== null && (
Pupils entered for EBacc {formatPercentage(latestResults.ebacc_entry_pct)}
)} {latestResults.ebacc_standard_pass_pct !== null && (
EBacc Grade 4+ {formatPercentage(latestResults.ebacc_standard_pass_pct)}
)} {latestResults.ebacc_strong_pass_pct !== null && (
EBacc Grade 5+ {formatPercentage(latestResults.ebacc_strong_pass_pct)}
)}
)} )}
)} {/* Year 1 Phonics — primary only */} {hasPhonics && isPrimary && phonics && (

Year 1 Phonics ({formatAcademicYear(phonics.year)})

Phonics is a key early reading skill. Children are tested at the end of Year 1.

Passed the phonics check
{formatPercentage(phonics.year1_phonics_pct)}
Phonics is a key early reading skill tested at end of Year 1
{phonics.year2_phonics_pct != null && (
Year 2 pupils who retook and passed
{formatPercentage(phonics.year2_phonics_pct)}
)}
)} {/* School Life */} {hasSchoolLife && (

School Life

{census?.class_size_avg != null && (
Average class size
{census.class_size_avg.toFixed(1)}
Average number of pupils per class
)} {absenceData?.overall_absence_rate != null && (
Days missed (overall absence)
{formatPercentage(absenceData.overall_absence_rate)}
{primaryAvg.overall_absence_pct != null && (
National avg: ~{primaryAvg.overall_absence_pct.toFixed(1)}%
)}
)} {absenceData?.persistent_absence_rate != null && (
Regularly missing school
{formatPercentage(absenceData.persistent_absence_rate)}
{primaryAvg.persistent_absence_pct != null && (
National avg: ~{primaryAvg.persistent_absence_pct.toFixed(0)}%
)}
)}
)} {/* How Hard to Get In */} {admissions && (

How Hard to Get Into This School ({formatAcademicYear(admissions.year)})

{admissions.published_admission_number != null && (
{isSecondary ? 'Year 7' : 'Year 3'} places per year
{admissions.published_admission_number}
)} {admissions.total_applications != null && (
Applications received
{admissions.total_applications.toLocaleString()}
)} {admissions.first_preference_offer_pct != null && (
Families who got their first-choice
{admissions.first_preference_offer_pct}%
)}
{admissions.oversubscribed != null && (
{admissions.oversubscribed ? '⚠ More applications than places last year' : '✓ Places were available last year'}
)}
)} {/* Pupils & Inclusion */} {hasInclusionData && (

Pupils & Inclusion

{latestResults?.disadvantaged_pct != null && (
Eligible for pupil premium
{formatPercentage(latestResults.disadvantaged_pct)}
Pupils from disadvantaged backgrounds
)} {latestResults?.eal_pct != null && (
English as an additional language
{formatPercentage(latestResults.eal_pct)}
)} {latestResults?.sen_support_pct != null && (
Pupils receiving SEN support
{formatPercentage(latestResults.sen_support_pct)}
)}
{senDetail && ( <>

Types of additional needs supported

What proportion of pupils with additional needs have each type of support need.

{[ { label: 'Speech & Language', pct: senDetail.primary_need_speech_pct }, { label: 'Autism (ASD)', pct: senDetail.primary_need_autism_pct }, { label: 'Learning Difficulties', pct: senDetail.primary_need_mld_pct }, { label: 'Specific Learning (e.g. Dyslexia)', pct: senDetail.primary_need_spld_pct }, { label: 'Social, Emotional & Mental Health', pct: senDetail.primary_need_semh_pct }, { label: 'Physical / Sensory', pct: senDetail.primary_need_physical_pct }, ].filter(n => n.pct != null).map(({ label, pct }) => (
{label}
{pct}%
))}
)}
)} {/* Location */} {hasLocation && (

Location

)} {/* Local Area Context */} {hasDeprivation && deprivation && (

Local Area Context

{Array.from({ length: 10 }, (_, i) => (
))}
Most deprived Least deprived

{deprivationDesc(deprivation.idaci_decile!)}

)} {/* Finances */} {hasFinance && finance && (

School Finances ({formatAcademicYear(finance.year)})

Per-pupil spending shows how much the school has to spend on each child's education.

Total spend per pupil per year
£{Math.round(finance.per_pupil_spend!).toLocaleString()}
How much the school has to spend on each pupil annually
{finance.teacher_cost_pct != null && (
Share of budget spent on teachers
{finance.teacher_cost_pct.toFixed(1)}%
)} {finance.staff_cost_pct != null && (
Share of budget spent on all staff
{finance.staff_cost_pct.toFixed(1)}%
)}
)} {/* Results Over Time (merged: chart + historical table) */} {yearlyData.length > 0 && (

Results Over Time

{yearlyData.length > 1 && ( <>

Detailed year-by-year figures

{isSecondary ? ( <> ) : ( <> )} {yearlyData.map((result) => ( {isSecondary ? ( <> ) : ( <> )} ))}
YearAttainment 8 Progress 8 English & Maths Grade 4+ English & Maths Grade 5+Reading, Writing & Maths (expected %) Exceeding expected (%) Reading Progress Writing Progress Maths Progress
{formatAcademicYear(result.year)}{result.attainment_8_score !== null ? result.attainment_8_score.toFixed(1) : '-'} {result.progress_8_score !== null ? formatProgress(result.progress_8_score) : '-'} {result.english_maths_standard_pass_pct !== null ? formatPercentage(result.english_maths_standard_pass_pct) : '-'} {result.english_maths_strong_pass_pct !== null ? formatPercentage(result.english_maths_strong_pass_pct) : '-'}{result.rwm_expected_pct !== null ? formatPercentage(result.rwm_expected_pct) : '-'} {result.rwm_high_pct !== null ? formatPercentage(result.rwm_high_pct) : '-'} {result.reading_progress !== null ? formatProgress(result.reading_progress) : '-'} {result.writing_progress !== null ? formatProgress(result.writing_progress) : '-'} {result.maths_progress !== null ? formatProgress(result.maths_progress) : '-'}
)}
)}
); }