PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 19s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m20s
Rewrites every visitor-facing string that used an em dash, choosing the punctuation for what the dash was doing: a colon before a list or explanation, a comma for an aside, a full stop between two thoughts, parentheses for an aside mid-sentence. Covers page titles and meta descriptions, the home and admissions guide copy, school page headings and notes, the compare page, metric labels and tooltips. Two rewrites also fix the sentence around them: the closure banner no longer repeats "proposed for closure", and the cut-off caveat's list of priorities now parses. A lone dash marking a missing value in a table cell stays: it is a data convention, not prose. A Jest guard walks the source with the TypeScript parser and fails on any other em dash in a string or JSX text node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
151 lines
6.8 KiB
TypeScript
151 lines
6.8 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 type { School, SchoolResult, NationalAverages } from '@/lib/types';
|
|
import { formatPercentage, formatProgress, formatAcademicYear } from '@/lib/utils';
|
|
import { Section, sectionStyles as styles } from './sectionShared';
|
|
import { PerformanceChart, SatsChart } from './charts';
|
|
|
|
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 & Maths Grade 4+</th>
|
|
</>
|
|
) : isSecondary ? (
|
|
<>
|
|
<th>Attainment 8</th>
|
|
<th>Progress 8</th>
|
|
<th>English & Maths Grade 4+</th>
|
|
<th>English & Maths Grade 5+</th>
|
|
</>
|
|
) : (
|
|
<>
|
|
<th>Reading, Writing & 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>
|
|
);
|
|
}
|