Files

129 lines
4.6 KiB
TypeScript
Raw Permalink Normal View History

/**
* Explore trends — the full grouped metric catalogue (nothing from the old
* compare page is lost; spec §4's tier 3) driving the year-by-year chart with
* its England reference line. Matches the mockup: a measure picker and the
* chart only (no data table).
*/
'use client';
import dynamic from 'next/dynamic';
import type { ComparisonData, MetricDefinition, NationalAverages, School } from '@/lib/types';
import { track } from '@/lib/analytics';
import { Section } from './sectionShared';
import styles from './TrendsExplorer.module.css';
const ComparisonChart = dynamic(
() => import('../ComparisonChart').then((m) => m.ComparisonChart),
{ ssr: false },
);
const PRIMARY_OPTGROUPS: { label: string; category: string }[] = [
{ label: 'Expected Standard', category: 'expected' },
{ label: 'Higher Standard', category: 'higher' },
{ label: 'Progress Scores', category: 'progress' },
{ label: 'Average Scores', category: 'average' },
{ label: 'Gender Performance', category: 'gender' },
{ label: 'Equity (Disadvantaged)', category: 'equity' },
{ label: 'School Context', category: 'context' },
{ label: 'Absence', category: 'absence' },
{ label: '3-Year Trends', category: 'trends' },
];
const SECONDARY_OPTGROUPS: { label: string; category: string }[] = [
{ label: 'GCSE Performance', category: 'gcse' },
];
export const PRIMARY_CATEGORIES = PRIMARY_OPTGROUPS.map((g) => g.category);
export const SECONDARY_CATEGORIES = SECONDARY_OPTGROUPS.map((g) => g.category);
export function TrendsExplorer({
schools,
data,
metrics,
metric,
onMetricChange,
isPrimaryPhase,
nationalAverages,
}: {
schools: School[];
data: Record<string, ComparisonData>;
metrics: MetricDefinition[];
/** Controlled: the page owns the metric so the URL contract survives. */
metric: string;
onMetricChange: (metric: string) => void;
isPrimaryPhase: boolean;
nationalAverages?: NationalAverages;
}) {
const allowedCategories = isPrimaryPhase ? PRIMARY_CATEGORIES : SECONDARY_CATEGORIES;
const optgroups = isPrimaryPhase ? PRIMARY_OPTGROUPS : SECONDARY_OPTGROUPS;
const filteredMetrics = metrics.filter((m) => allowedCategories.includes(m.category));
const metricDef = metrics.find((m) => m.key === metric);
const metricLabel = metricDef?.label || metric;
const nationalByYear: Record<number, number | null> = {};
for (const entry of nationalAverages?.by_year ?? []) {
const block = isPrimaryPhase ? entry.primary : entry.secondary;
nationalByYear[entry.year] = block?.[metric] ?? null;
}
const handleMetricChange = (next: string) => {
track('compare_metric_changed', { metric: next, phase: isPrimaryPhase ? 'primary' : 'secondary' });
onMetricChange(next);
};
return (
<Section
title="Explore trends"
how="The full year-by-year explorer for any measure, with the England average as a dashed reference line where official figures exist."
>
<details className={styles.explore} open>
<summary>Year-by-year trends</summary>
<div className={styles.inner}>
<div className={styles.picker}>
<label htmlFor="trends-metric-select">Measure:</label>
<select
id="trends-metric-select"
value={metric}
onChange={(e) => handleMetricChange(e.target.value)}
>
{optgroups.map(({ label, category }) => {
const group = filteredMetrics.filter((m) => m.category === category);
if (group.length === 0) return null;
return (
<optgroup key={category} label={label}>
{group.map((m) => (
<option key={m.key} value={m.key}>
{m.label}
</option>
))}
</optgroup>
);
})}
</select>
{metricDef?.description && <span className={styles.desc}>{metricDef.description}</span>}
</div>
{metric.includes('progress') && (
<p className={styles.progressNote}>
Progress scores measure pupils&apos; progress from KS1 to KS2. A score of 0 equals the
national average. DfE stopped publishing KS2 progress after 2022/23 (no KS1 baseline).
</p>
)}
<div className={styles.chartBox}>
<ComparisonChart
comparisonData={data}
schools={schools}
metric={metric}
metricLabel={metricLabel}
nationalByYear={nationalByYear}
/>
</div>
</div>
</details>
</Section>
);
}