123 lines
5.4 KiB
TypeScript
123 lines
5.4 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>
|
||
|
|
)}
|
||
|
|
{(() => {
|
||
|
|
const total = census?.total_pupils ?? schoolInfo.total_pupils ?? latestResults?.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>
|
||
|
|
);
|
||
|
|
}
|