82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
/**
|
|||
|
|
* CutoffYearTable — the last distance offered, year by year.
|
||
|
|
*
|
||
|
|
* The table is where the chart's gaps get explained. Each year says which of
|
||
|
|
* the three things happened, in words, because "no figure" alone is the least
|
||
|
|
* useful thing we could tell a parent about a year.
|
||
|
|
*
|
||
|
|
* Server component: it is static markup over data the page already holds.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import type { CutoffYearRow, CutoffYearStatus } from './lastDistanceOffered';
|
||
|
|
import { sectionStyles as styles } from './sectionShared';
|
||
|
|
|
||
|
|
const STATUS_LABEL: Record<CutoffYearStatus, string> = {
|
||
|
|
published: 'Oversubscribed',
|
||
|
|
// Precisely what the EES field supports, and no more: first preferences did
|
||
|
|
// not exceed places. It does not establish that every applicant was offered
|
||
|
|
// a place, so it must not be worded as though it did.
|
||
|
|
'not-oversubscribed': 'Places available on first preferences',
|
||
|
|
'not-published': 'Not published',
|
||
|
|
};
|
||
|
|
|
||
|
|
const STATUS_CLASS: Record<CutoffYearStatus, string> = {
|
||
|
|
published: styles.cutoffPillOver,
|
||
|
|
'not-oversubscribed': styles.cutoffPillOk,
|
||
|
|
'not-published': styles.cutoffPillNa,
|
||
|
|
};
|
||
|
|
|
||
|
|
const DISTANCE_TEXT: Record<CutoffYearStatus, string> = {
|
||
|
|
published: '',
|
||
|
|
'not-oversubscribed': 'No cut-off needed',
|
||
|
|
'not-published': '—',
|
||
|
|
};
|
||
|
|
|
||
|
|
export function CutoffYearTable({ rows }: { rows: CutoffYearRow[] }) {
|
||
|
|
if (rows.length === 0) return null;
|
||
|
|
|
||
|
|
const showPlaces = rows.some((r) => r.placesOffered != null);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={styles.cutoffTableWrap}>
|
||
|
|
<table className={styles.cutoffTable}>
|
||
|
|
<caption className={styles.srOnly}>
|
||
|
|
Last distance offered by year, with the reason where no figure was published
|
||
|
|
</caption>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th scope="col">Year</th>
|
||
|
|
<th scope="col">Last distance</th>
|
||
|
|
{showPlaces && <th scope="col">Places</th>}
|
||
|
|
<th scope="col">Status</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
{rows.map((r, i) => (
|
||
|
|
<tr key={r.year} className={i === 0 ? styles.cutoffRowLatest : undefined}>
|
||
|
|
<th scope="row">{r.year}</th>
|
||
|
|
<td className={r.status === 'published' ? undefined : styles.cutoffCellMissing}>
|
||
|
|
{r.status === 'published' ? r.figure : DISTANCE_TEXT[r.status]}
|
||
|
|
{r.status === 'published' && (r.routeCount ?? 1) > 1 && (
|
||
|
|
<span className={styles.cutoffRouteFlag}>
|
||
|
|
{' '}furthest of {r.routeCount}
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</td>
|
||
|
|
{showPlaces && (
|
||
|
|
<td className={r.placesOffered == null ? styles.cutoffCellMissing : undefined}>
|
||
|
|
{r.placesOffered ?? '—'}
|
||
|
|
</td>
|
||
|
|
)}
|
||
|
|
<td>
|
||
|
|
<span className={`${styles.cutoffPill} ${STATUS_CLASS[r.status]}`}>
|
||
|
|
{STATUS_LABEL[r.status]}
|
||
|
|
</span>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
))}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|