Files
school_compare/nextjs-app/components/school/SecondarySchoolSections.tsx
TudorandClaude Opus 5 752eb07310 refactor(detail): render sections on the server behind a client shell
page.tsx now composes the sections and passes them through SchoolDetailShell
as children, so ~1,300 lines of static markup stop shipping as client
JavaScript. The shell keeps what is genuinely interactive: back link, header
reveal, hero map, compare CTA, sticky nav and scroll-spy.

The scroll-spy already located sections via document.getElementById, so it
works unchanged against server-rendered children.

Charts needed a client wrapper: next/dynamic with ssr:false is illegal in a
Server Component, so components/school/charts.tsx is the boundary that keeps
Chart.js (64 KB gz) lazy and browser-only.

Measured on this build:
- school route client chunk: 8 KB gz (33 KB raw)
- total static JS across all chunks: 380.6 -> 350.7 KB gz
- section markup is absent from every client chunk ("Got their first choice",
  "Ofsted reports", "Most deprived" etc. all return 0 hits); shell strings
  still present, as expected
- shared baseline unchanged at 172 KB gz -- out of scope, as designed

The 14 characterization tests pass byte-identical to the commit that
introduced them. Only the render helper changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 21:39:21 +01:00

116 lines
3.7 KiB
TypeScript

/**
* SecondarySchoolSections — the section sequence for secondary detail pages.
* Server component.
*
* Wrapped in `.secondaryScope`, which activates the secondary-only style
* overrides in schoolSections.module.css. Those rules target class names the
* primary page also uses (.card, .sectionTitle, .metricCard …), so scoping is
* what keeps them from restyling primary pages.
*
* The render conditions here MUST match buildSecondaryNavItems in
* lib/schoolSections, or the sticky nav will link to sections that do not exist.
*/
import type {
School, SchoolResult, AbsenceData, OfstedInspection, SchoolCensus,
SchoolAdmissions, SchoolDeprivation, SchoolFinance, NationalAverages,
} from '@/lib/types';
import { ofstedLegacyAreas } from '@/lib/utils';
import type { SecondaryFlags } from '@/lib/schoolSections';
import { OfstedSection } from './OfstedSection';
import { GcseSection } from './GcseSection';
import { SecondaryAdmissionsSection } from './SecondaryAdmissionsSection';
import { SecondaryHistorySection } from './SecondaryHistorySection';
import { WellbeingSection } from './WellbeingSection';
import { FinancesSection } from './FinancesSection';
import styles from './schoolSections.module.css';
export interface SecondarySchoolSectionsProps {
schoolInfo: School;
yearlyData: SchoolResult[];
absenceData: AbsenceData | null;
ofsted: OfstedInspection | null;
census: SchoolCensus | null;
admissions: SchoolAdmissions | null;
deprivation: SchoolDeprivation | null;
finance: SchoolFinance | null;
nationalAvg: NationalAverages | null;
flags: SecondaryFlags;
}
export function SecondarySchoolSections({
schoolInfo, yearlyData, ofsted, census,
admissions, deprivation, finance, nationalAvg, flags,
}: SecondarySchoolSectionsProps) {
const secondaryAvg = nationalAvg?.secondary ?? {};
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
const ofstedInspectedDate = isReportCard
? ofsted?.rc_inspection_date ?? null
: ofsted?.inspection_date ?? null;
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
const oeifAllSameGrade =
!!ofsted &&
!isReportCard &&
oeifAreas.length >= 3 &&
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
return (
<div className={styles.secondaryScope}>
{ofsted && (
<OfstedSection
ofsted={ofsted}
urn={schoolInfo.urn}
isReportCard={isReportCard}
ofstedInspectedDate={ofstedInspectedDate}
oeifAllSameGrade={oeifAllSameGrade}
oeifAreas={oeifAreas}
variant="secondary"
/>
)}
{flags.hasResults && flags.latestResults && (
<GcseSection
latestResults={flags.latestResults}
schoolInfo={schoolInfo}
secondaryAvg={secondaryAvg}
p8Suspended={flags.p8Suspended}
suppressComparison={flags.suppressComparison}
/>
)}
{admissions && (
<SecondaryAdmissionsSection
admissions={admissions}
schoolInfo={schoolInfo}
hasSixthForm={flags.hasSixthForm}
/>
)}
{yearlyData.length > 1 && (
<SecondaryHistorySection
yearlyData={yearlyData}
schoolInfo={schoolInfo}
nationalAvg={nationalAvg}
secondaryAvg={secondaryAvg}
suppressComparison={flags.suppressComparison}
/>
)}
{flags.hasWellbeing && (
<WellbeingSection
latestResults={flags.latestResults}
census={census}
schoolInfo={schoolInfo}
deprivation={deprivation}
hasDeprivation={flags.hasDeprivation}
/>
)}
{flags.hasFinance && finance && (
<FinancesSection finance={finance} showPremises />
)}
</div>
);
}