/** * NearbySchoolsSection — the nearest eligible schools, closest first. Server * component; only the carousel, the compare bar and the add-to-compare button * are client-side. * * "Other schools nearby", not "similar" ones: the order is distance and only * distance. The hard filters upstream still guarantee the set is comparable — * same phase, same selectivity, mainstream never beside special — but nothing * here ranks by how alike two schools are, so the heading does not say it does. * * The chips report what a school shares, and may be absent entirely. That is * information for the reader to weigh, not a verdict this section has already * reached on their behalf. * * There is deliberately no "how these are chosen" panel: the method is already * visible in the lede, the chips and the distances. The single caption line is * not a method note — it is the one thing a card cannot self-correct. */ import Link from 'next/link'; import type { NearbySchool } from '@/lib/types'; import { schoolUrl } from '@/lib/utils'; import { AddToCompareButton } from './AddToCompareButton'; import { NearbySchoolsCarousel } from './NearbySchoolsCarousel'; import { NearbySchoolsCompareBar } from './NearbySchoolsCompareBar'; import { Section } from './sectionShared'; import styles from './NearbySchools.module.css'; const MINIMUM = 2; export function shouldRenderNearby(nearby?: NearbySchool[] | null): boolean { return (nearby?.length ?? 0) >= MINIMUM; } /** * What the lede calls the set of schools it is showing. * * Derived from the school's own GIAS phase rather than the template it renders * with, because those disagree for "16 plus" (GIAS phase 6): a sixth-form * college renders the primary template — computeSchoolFlags tests for the * substring "secondary" — while the backend correctly matches it against the * secondary group. Taking the noun from the template would print "Other primary * schools near " above a row of secondaries. * * A 16-plus school's candidates span the whole secondary group, so no single * noun fits and it gets the honest general one. */ export function nearbyNoun(phase: string | null | undefined): string { const text = (phase ?? '').trim().toLowerCase(); if (text === 'all-through') return 'all-through schools'; if (text === '16 plus') return 'schools and colleges'; if (text.includes('secondary')) return 'secondary schools'; if (text.includes('primary')) return 'primary schools'; return 'schools'; } function metricLabel(key: string): string { return key === 'attainment_8_score' ? 'Attainment 8' : 'Reading, writing & maths'; } function formatMetric(value: number | null, key: string): string { if (value == null) return 'Not published'; return key === 'attainment_8_score' ? value.toFixed(1) : `${Math.round(value)}%`; } export function NearbySchoolsSection({ urn, schoolName, phase, thisMetricValue, nearby, }: { urn: number; schoolName: string; /** The school's own GIAS phase, not the template it renders with. */ phase: string | null | undefined; thisMetricValue: number | null; nearby?: NearbySchool[] | null; }) { if (!shouldRenderNearby(nearby)) return null; const schools = nearby as NearbySchool[]; // One card matched on phase alone, so the section may not claim the set // shares an intake with this school. const metricKey = schools[0].metric_key; const noun = nearbyNoun(phase); return (

Other schools nearby

{`Other ${noun} near ${schoolName}.`}

} > {schools.map((school) => (
  • {school.distance_miles} miles away

    {school.school_name}

    {[school.school_type, school.age_range && `Ages ${school.age_range}`] .filter(Boolean) .join(' · ')}

    {school.shared.length > 0 && (
      {school.shared.map((label) => (
    • {label}
    • ))}
    )}

    {formatMetric(school.metric_value, school.metric_key)}

    {metricLabel(school.metric_key)}

    {thisMetricValue != null && (

    {formatMetric(thisMetricValue, metricKey)} at this school

    )}
  • ))}
    {/* The one caveat the cards cannot make on their own: a reader who takes "0.6 miles away" for the walk has been misled, and nothing else here corrects that. */}

    Distances are straight-line from this school, not road distance.

    ); }