125 lines
4.7 KiB
TypeScript
125 lines
4.7 KiB
TypeScript
/**
|
||||
|
|
* Getting a place — admissions framed the way the expert review requires:
|
|||
|
|
* total applications are "named on N forms" (any preference rank, not
|
|||
|
|
* head-to-head), one consistent chip metric (first-preference success),
|
|||
|
|
* equal-preference and offers-vs-intake explanations up front.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
'use client';
|
|||
|
|
|
|||
|
|
import { summariseAdmissions } from '@/lib/compareLogic';
|
|||
|
|
import type { ComparisonData, School } from '@/lib/types';
|
|||
|
|
import { CHART_COLORS } from '@/lib/utils';
|
|||
|
|
import { Cell, Chip, RowLabel, Section, SectionGrid, sectionStyles as s } from './sectionShared';
|
|||
|
|
|
|||
|
|
export function CompareAdmissions({
|
|||
|
|
schools,
|
|||
|
|
data,
|
|||
|
|
}: {
|
|||
|
|
schools: School[];
|
|||
|
|
data: Record<string, ComparisonData>;
|
|||
|
|
}) {
|
|||
|
|
const rows = schools.map((school) => data[String(school.urn)]?.admissions ?? null);
|
|||
|
|
const anyData = rows.some(Boolean);
|
|||
|
|
const entryYear = rows.find(Boolean)?.year;
|
|||
|
|
const entryLabel = entryYear
|
|||
|
|
? `September ${String(entryYear).slice(0, 4)} entry`
|
|||
|
|
: 'the most recent admissions round';
|
|||
|
|
|
|||
|
|
if (!anyData) {
|
|||
|
|
return (
|
|||
|
|
<Section title="Getting a place" how="No admissions data is available for these schools yet.">
|
|||
|
|
<></>
|
|||
|
|
</Section>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Section
|
|||
|
|
title="Getting a place"
|
|||
|
|
how={
|
|||
|
|
<>
|
|||
|
|
From the most recent admissions round ({entryLabel}). "First choice" means
|
|||
|
|
families who ranked the school top of their application form — officially a "first
|
|||
|
|
preference". Schools never see your ranking: places are decided only by the
|
|||
|
|
school's admission criteria, so listing a school lower down never hurts your chances.
|
|||
|
|
These are National Offer Day offers — waiting lists and appeals can change the final
|
|||
|
|
intake.
|
|||
|
|
</>
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
<SectionGrid schools={schools}>
|
|||
|
|
<RowLabel tip="How many application forms named the school at any preference rank — not the number of families competing head-to-head for a place.">
|
|||
|
|
Interest in the school
|
|||
|
|
</RowLabel>
|
|||
|
|
{schools.map((school, i) => {
|
|||
|
|
const a = rows[i];
|
|||
|
|
return (
|
|||
|
|
<Cell key={school.urn} school={school} index={i}>
|
|||
|
|
{a?.total_applications != null && a?.places_offered != null ? (
|
|||
|
|
<>
|
|||
|
|
Named on <strong>{a.total_applications.toLocaleString('en-GB')}</strong> forms ·{' '}
|
|||
|
|
<strong>{a.places_offered.toLocaleString('en-GB')}</strong> places
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
<span className={s.small}>No data</span>
|
|||
|
|
)}
|
|||
|
|
</Cell>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
|
|||
|
|
<RowLabel>First-choice families offered a place</RowLabel>
|
|||
|
|
{schools.map((school, i) => {
|
|||
|
|
const summary = summariseAdmissions(rows[i]);
|
|||
|
|
return (
|
|||
|
|
<Cell key={school.urn} school={school} index={i}>
|
|||
|
|
{summary.firstPrefPct != null ? (
|
|||
|
|
<>
|
|||
|
|
<strong>{summary.firstPrefPct}%</strong>{' '}
|
|||
|
|
{summary.chip && summary.chip.tone === 'warn' && (
|
|||
|
|
<Chip tone="warn">{summary.chip.text}</Chip>
|
|||
|
|
)}
|
|||
|
|
<span className={s.barMini}>
|
|||
|
|
<i
|
|||
|
|
style={{
|
|||
|
|
width: `${summary.firstPrefPct}%`,
|
|||
|
|
background: CHART_COLORS[i % CHART_COLORS.length],
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</span>
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
<span className={s.small}>No data</span>
|
|||
|
|
)}
|
|||
|
|
</Cell>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
|
|||
|
|
<RowLabel>What this means</RowLabel>
|
|||
|
|
{schools.map((school, i) => {
|
|||
|
|
const a = rows[i];
|
|||
|
|
const summary = summariseAdmissions(a);
|
|||
|
|
let text: string | null = null;
|
|||
|
|
if (summary.firstPrefPct != null) {
|
|||
|
|
if (summary.firstPrefPct >= 100) {
|
|||
|
|
text = `Every family who put ${school.school_name} first got a place.`;
|
|||
|
|
} else if (summary.firstPrefPct >= 90) {
|
|||
|
|
text = `Nearly every family who put ${school.school_name} first got a place.`;
|
|||
|
|
} else if (a?.oversubscribed) {
|
|||
|
|
text =
|
|||
|
|
'More first-choice applications than places — check the school’s admission criteria (for most non-faith primaries, distance decides).';
|
|||
|
|
} else {
|
|||
|
|
text = `${summary.firstPrefPct}% of first-choice families received an offer.`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return (
|
|||
|
|
<Cell key={school.urn} school={school} index={i}>
|
|||
|
|
{text ? <span className={s.small}>{text}</span> : <span className={s.small}>—</span>}
|
|||
|
|
</Cell>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</SectionGrid>
|
|||
|
|
</Section>
|
|||
|
|
);
|
|||
|
|
}
|