'use client'; import { useEffect, useRef } from 'react'; import styles from './SatsChart.module.css'; interface SubjectData { name: string; expectedPct: number | null; exceedingPct: number | null; nationalExpectedPct: number | null; /** National higher-standard / greater-depth benchmark for the exceeding * bar — a different figure from the expected national. */ nationalExceedingPct: number | null; } interface SatsChartProps { subjects: SubjectData[]; } const RULER_TICKS = [0, 25, 50, 75, 100]; const GRIDLINE_POSITIONS = [25, 50, 75]; /** One results bar with a national marker positioned on its own track, so the * expected and exceeding bars each compare against their own benchmark. */ function BarRow({ label, value, national, barRef, barClass, }: { label: string; value: number; national: number | null; barRef: React.RefObject; barClass: string; }) { return (
{label} {national != null && nat {national.toFixed(0)}%} {value.toFixed(0)}%
{national != null && (
); } function SubjectColumn({ subject }: { subject: SubjectData }) { const expectedRef = useRef(null); const exceedingRef = useRef(null); const { name, expectedPct, exceedingPct, nationalExpectedPct, nationalExceedingPct } = subject; // Animate bars on mount useEffect(() => { const bars = [expectedRef.current, exceedingRef.current]; bars.forEach((bar) => { if (!bar) return; const target = bar.dataset.width; bar.style.width = '0%'; requestAnimationFrame(() => { requestAnimationFrame(() => { bar.style.width = `${target}%`; }); }); }); }, [expectedPct, exceedingPct]); if (expectedPct == null && exceedingPct == null) return null; return (
{name}
{/* Gridlines */}
{GRIDLINE_POSITIONS.map((pct) => (
))}
{/* Bars — each carries its own national marker */}
{expectedPct != null && ( )} {exceedingPct != null && ( )}
{/* Ruler */}
{RULER_TICKS.map((pct, i) => (
{pct}%
))}
); } export default function SatsChart({ subjects }: SatsChartProps) { const visibleSubjects = subjects.filter( (s) => s.expectedPct != null || s.exceedingPct != null ); if (visibleSubjects.length === 0) return null; return (
{visibleSubjects.map((subject) => ( ))}
Expected standard
Exceeding / high score
National average
); }