Files
school_compare/nextjs-app/components/school/HistorySection.tsx
T
TudorandClaude Opus 5 a7f6ff4035 refactor(detail): extract sections as server components
Moves ~1,300 lines of section markup out of the two client views into
components/school/, mirroring the components/compare/ layout. Twelve section
components plus shared primitives, all server components. The only client
file is AdmissionsViewToggle, which owns the hidden/aria-pressed state and
receives both views as server-rendered children.

JSX was extracted mechanically rather than retyped, so the markup the CSS
modules depend on is verbatim.

Sharing follows measured similarity, not assumption:
- Finances (91%) shared. The secondary premises-cost card is gated behind a
  prop so primary pages are unchanged; enabling it is a one-line follow-up.
- Ofsted (80%) shared, but behind a variant prop. The headline similarity hid
  a real fork: on a school with no overall grade the primary page shows a
  "Not rated" badge while the secondary shows a four-area OEIF panel, and the
  disclaimer copy differs. Both preserved exactly; reconciling them is a
  human decision, not a side effect of a move.
- Admissions (14%) and History (40%) kept separate.

Not yet wired up -- the old views still render.

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

157 lines
7.0 KiB
TypeScript

/**
* HistorySection — results over time (chart plus historical table).
* Primary pages; the secondary equivalent is SecondaryHistorySection (the two
* were only 40% similar). Server component.
*/
import dynamic from 'next/dynamic';
import type { School, SchoolResult, NationalAverages } from '@/lib/types';
import { formatPercentage, formatProgress, formatAcademicYear } from '@/lib/utils';
import { Section, sectionStyles as styles } from './sectionShared';
const PerformanceChart = dynamic(
() => import('../PerformanceChart').then((m) => m.PerformanceChart),
{ ssr: false },
);
const SatsChart = dynamic(() => import('../SatsChart'), { ssr: false });
export function HistorySection({
yearlyData, schoolInfo, nationalAvg, primaryAvg, secondaryAvg,
isAllThrough, isPrimary, isSecondary, hasKS2Results, hasKS4Results,
suppressKs2Comparison, suppressKs4Comparison,
}: {
yearlyData: SchoolResult[];
schoolInfo: School;
nationalAvg: NationalAverages | null;
primaryAvg: Record<string, number>;
secondaryAvg: Record<string, number>;
isAllThrough: boolean;
isPrimary: boolean;
isSecondary: boolean;
hasKS2Results: boolean;
hasKS4Results: boolean;
suppressKs2Comparison: boolean;
suppressKs4Comparison: boolean;
}) {
return (
<section id="history" className={styles.card}>
<h2 className={styles.sectionTitle}>Results Over Time</h2>
{isAllThrough ? (
// All-through: KS2 and KS4 trends are on different scales and have
// different gap stories, so render them as two stacked charts
// rather than crowding 8+ series onto one axis.
<>
{hasKS2Results && (
<>
<h3 className={styles.subSectionTitle}>Primary KS2 SATs</h3>
<div className={styles.chartContainer}>
<PerformanceChart
data={yearlyData}
schoolName={schoolInfo.school_name}
isSecondary={false}
nationalRwmAvg={suppressKs2Comparison ? null : (primaryAvg.rwm_expected_pct ?? null)}
nationalByYear={suppressKs2Comparison ? undefined : nationalAvg?.by_year}
/>
</div>
</>
)}
{hasKS4Results && (
<>
<h3 className={styles.subSectionTitle} style={{ marginTop: '1.5rem' }}>Secondary GCSEs</h3>
<div className={styles.chartContainer}>
<PerformanceChart
data={yearlyData}
schoolName={schoolInfo.school_name}
isSecondary={true}
nationalAtt8Avg={suppressKs4Comparison ? null : (secondaryAvg.attainment_8_score ?? null)}
nationalByYear={suppressKs4Comparison ? undefined : nationalAvg?.by_year}
/>
</div>
</>
)}
</>
) : (
<div className={styles.chartContainer}>
<PerformanceChart
data={yearlyData}
schoolName={schoolInfo.school_name}
isSecondary={isSecondary}
nationalRwmAvg={isPrimary && !suppressKs2Comparison ? (primaryAvg.rwm_expected_pct ?? null) : null}
nationalAtt8Avg={isSecondary && !suppressKs4Comparison ? (secondaryAvg.attainment_8_score ?? null) : null}
nationalByYear={(isPrimary ? suppressKs2Comparison : suppressKs4Comparison) ? undefined : nationalAvg?.by_year}
/>
</div>
)}
{yearlyData.length > 1 && (
<details className={styles.historyDisclosure}>
<summary className={styles.historyToggle}>View raw year-by-year data</summary>
<div className={styles.tableWrapper}>
<table className={styles.dataTable}>
<thead>
<tr>
<th>Year</th>
{isAllThrough ? (
<>
<th>RWM (expected %)</th>
<th>Exceeding (%)</th>
<th>Attainment 8</th>
<th>Progress 8</th>
<th>English &amp; Maths Grade 4+</th>
</>
) : isSecondary ? (
<>
<th>Attainment 8</th>
<th>Progress 8</th>
<th>English &amp; Maths Grade 4+</th>
<th>English &amp; Maths Grade 5+</th>
</>
) : (
<>
<th>Reading, Writing &amp; Maths (expected %)</th>
<th>Exceeding expected (%)</th>
<th>Reading Progress</th>
<th>Writing Progress</th>
<th>Maths Progress</th>
</>
)}
</tr>
</thead>
<tbody>
{yearlyData.map((result) => (
<tr key={result.year}>
<td className={styles.yearCell}>{formatAcademicYear(result.year)}</td>
{isAllThrough ? (
<>
<td>{result.rwm_expected_pct !== null ? formatPercentage(result.rwm_expected_pct) : '-'}</td>
<td>{result.rwm_high_pct !== null ? formatPercentage(result.rwm_high_pct) : '-'}</td>
<td>{result.attainment_8_score !== null ? result.attainment_8_score.toFixed(1) : '-'}</td>
<td>{result.progress_8_score !== null ? formatProgress(result.progress_8_score) : '-'}</td>
<td>{result.english_maths_standard_pass_pct !== null ? formatPercentage(result.english_maths_standard_pass_pct) : '-'}</td>
</>
) : isSecondary ? (
<>
<td>{result.attainment_8_score !== null ? result.attainment_8_score.toFixed(1) : '-'}</td>
<td>{result.progress_8_score !== null ? formatProgress(result.progress_8_score) : '-'}</td>
<td>{result.english_maths_standard_pass_pct !== null ? formatPercentage(result.english_maths_standard_pass_pct) : '-'}</td>
<td>{result.english_maths_strong_pass_pct !== null ? formatPercentage(result.english_maths_strong_pass_pct) : '-'}</td>
</>
) : (
<>
<td>{result.rwm_expected_pct !== null ? formatPercentage(result.rwm_expected_pct) : '-'}</td>
<td>{result.rwm_high_pct !== null ? formatPercentage(result.rwm_high_pct) : '-'}</td>
<td>{result.reading_progress !== null ? formatProgress(result.reading_progress) : '-'}</td>
<td>{result.writing_progress !== null ? formatProgress(result.writing_progress) : '-'}</td>
<td>{result.maths_progress !== null ? formatProgress(result.maths_progress) : '-'}</td>
</>
)}
</tr>
))}
</tbody>
</table>
</div>
</details>
)}
</section>
);
}