fact_performance's total_pupils is the cohort a year's results were measured on. For a secondary that is Year 11 alone, and the list API sent it as the card's "pupils": Burntwood showed 245 in search and 1,462 on its page. The list and place payloads now carry the register's whole-school count, and nothing when the register has none. Map popups and the compare basket read the same field. The header and the wellbeing section fell back to the same results figure when the census had no record. They now fall back to the register count. The shell took yearlyData only for that fallback, so the prop is gone and the results array no longer ships to the client for the chrome. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
124 lines
5.5 KiB
TypeScript
124 lines
5.5 KiB
TypeScript
/**
|
|
* WellbeingSection — SEN, gender split and local-area deprivation.
|
|
* Secondary pages. Server component.
|
|
*/
|
|
|
|
import type { School, SchoolCensus, SchoolResult, SchoolDeprivation } from '@/lib/types';
|
|
import { formatPercentage } from '@/lib/utils';
|
|
import { MetricTooltip } from '../MetricTooltip';
|
|
import { Section, sectionStyles as styles } from './sectionShared';
|
|
|
|
// Moved with this section from SecondarySchoolDetailView, its only consumer.
|
|
function deprivationDesc(decile: number) {
|
|
if (decile <= 3) return `This school is in one of England's most deprived areas (decile ${decile}/10). Many pupils may face additional challenges at home.`;
|
|
if (decile <= 7) return `This school is in an area with average levels of deprivation (decile ${decile}/10).`;
|
|
return `This school is in one of England's less deprived areas (decile ${decile}/10).`;
|
|
}
|
|
|
|
export function WellbeingSection({
|
|
latestResults, census, schoolInfo, deprivation, hasDeprivation,
|
|
}: {
|
|
latestResults: SchoolResult | null;
|
|
census: SchoolCensus | null;
|
|
schoolInfo: School;
|
|
deprivation: SchoolDeprivation | null;
|
|
hasDeprivation: boolean;
|
|
}) {
|
|
return (
|
|
<section id="wellbeing" className={styles.card}>
|
|
<h2 className={styles.sectionTitle}>Wellbeing & Context</h2>
|
|
|
|
{/* SEN */}
|
|
{(latestResults?.sen_support_pct != null || latestResults?.sen_ehcp_pct != null) && (
|
|
<>
|
|
<h3 className={styles.subSectionTitle}>Special Educational Needs (SEN)</h3>
|
|
<div className={styles.heroStatGrid}>
|
|
{latestResults?.sen_support_pct != null && (
|
|
<div className={styles.heroStatCard}>
|
|
<div className={styles.heroStatLabel}>
|
|
SEN support
|
|
<MetricTooltip metricKey="sen_support_pct" />
|
|
</div>
|
|
<div className={styles.heroStatValue}>{formatPercentage(latestResults.sen_support_pct)}</div>
|
|
<div className={styles.heroStatHint}>Without an EHCP</div>
|
|
</div>
|
|
)}
|
|
{latestResults?.sen_ehcp_pct != null && (
|
|
<div className={styles.heroStatCard}>
|
|
<div className={styles.heroStatLabel}>
|
|
Pupils with EHCP
|
|
<MetricTooltip metricKey="sen_ehcp_pct" />
|
|
</div>
|
|
<div className={styles.heroStatValue}>{formatPercentage(latestResults.sen_ehcp_pct)}</div>
|
|
<div className={styles.heroStatHint}>Education, Health and Care Plan</div>
|
|
</div>
|
|
)}
|
|
{(() => {
|
|
// Not latestResults.total_pupils: that is the GCSE year group.
|
|
const total = census?.total_pupils ?? schoolInfo.total_pupils ?? null;
|
|
if (total == null) return null;
|
|
const female = census?.female_pupils ?? null;
|
|
const male = census?.male_pupils ?? null;
|
|
const isMixed = schoolInfo.gender === 'Mixed' || schoolInfo.gender == null;
|
|
const hasSplit = isMixed && female != null && male != null && female + male > 0;
|
|
const sum = hasSplit ? female! + male! : 0;
|
|
const girlsPct = hasSplit ? Math.round((female! / sum) * 100) : 0;
|
|
const boysPct = hasSplit ? 100 - girlsPct : 0;
|
|
return (
|
|
<div className={styles.heroStatCard}>
|
|
<div className={styles.heroStatLabel}>Total pupils</div>
|
|
<div className={styles.heroStatValue}>{total.toLocaleString()}</div>
|
|
{hasSplit && (
|
|
<>
|
|
<div
|
|
className={styles.genderBar}
|
|
role="img"
|
|
aria-label={`Gender split: ${girlsPct}% girls, ${boysPct}% boys`}
|
|
>
|
|
<span className={styles.genderBarGirls} style={{ width: `${girlsPct}%` }} />
|
|
<span className={styles.genderBarBoys} style={{ width: `${boysPct}%` }} />
|
|
</div>
|
|
<div className={styles.genderSplitHint}>
|
|
<span className={styles.genderSplitGirls}>{girlsPct}% girls</span>
|
|
<span className={styles.genderSplitSep}> · </span>
|
|
<span className={styles.genderSplitBoys}>{boysPct}% boys</span>
|
|
</div>
|
|
</>
|
|
)}
|
|
{schoolInfo.capacity != null && !hasSplit && (
|
|
<div className={styles.heroStatHint}>Capacity: {schoolInfo.capacity}</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Deprivation */}
|
|
{hasDeprivation && deprivation && (
|
|
<>
|
|
<h3 className={styles.subSectionTitle} style={{ marginTop: '1.25rem' }}>
|
|
Local Area Context
|
|
<MetricTooltip metricKey="idaci_decile" />
|
|
</h3>
|
|
<div className={styles.deprivationDots}>
|
|
{Array.from({ length: 10 }, (_, i) => (
|
|
<div
|
|
key={i}
|
|
className={`${styles.deprivationDot} ${i < deprivation.idaci_decile! ? styles.deprivationDotFilled : ''}`}
|
|
title={`Decile ${i + 1}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
<div className={styles.deprivationScaleLabel}>
|
|
<span>Most deprived</span>
|
|
<span>Least deprived</span>
|
|
</div>
|
|
<p className={styles.deprivationDesc}>{deprivationDesc(deprivation.idaci_decile!)}</p>
|
|
</>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|