feat(detail): surface GIAS identity/contact details, drop unwired sections
PR Checks / Frontend Typecheck + Tests (pull_request) Canceled after 58s
PR Checks / Backend Smoke (pull_request) Canceled after 0s
PR Checks / Build Backend (no push) (pull_request) Canceled after 0s
PR Checks / Build Frontend (no push) (pull_request) Canceled after 0s
PR Checks / Build Pipeline (no push) (pull_request) Canceled after 0s
PR Checks / AI Code Review (Claude) (pull_request) Canceled after 0s
PR Checks / Frontend Typecheck + Tests (pull_request) Canceled after 58s
PR Checks / Backend Smoke (pull_request) Canceled after 0s
PR Checks / Build Backend (no push) (pull_request) Canceled after 0s
PR Checks / Build Frontend (no push) (pull_request) Canceled after 0s
PR Checks / Build Pipeline (no push) (pull_request) Canceled after 0s
PR Checks / AI Code Review (Claude) (pull_request) Canceled after 0s
Add seven school-identity fields to the detail header (both primary and secondary views): age range, religious character, nursery and sixth-form indicators as chips; telephone (tel: link), county and parliamentary constituency as header details. religious_denomination, age_range and has_sixth_form were already served; telephone, nursery_provision, county and parliamentary_constituency are newly wired through the marts query (with a NULL fallback for un-rebuilt marts, mirroring has_sixth_form) and the school_info API response. Remove three UI sections the backend never populated (always null): Year 1 Phonics, the SEN "types of additional needs" breakdown, and the average class-size card — along with their now-dead props, route plumbing, and the SenDetail/Phonics types + class_size_avg field. Extend the e2e detail journey to assert the Phonics section is gone and the new header fields render when the record carries them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,11 +14,11 @@ import { MetricTooltip } from './MetricTooltip';
|
||||
import type {
|
||||
School, SchoolResult, AbsenceData,
|
||||
OfstedInspection, SchoolCensus,
|
||||
SchoolAdmissions, SenDetail, Phonics,
|
||||
SchoolAdmissions,
|
||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||
} from '@/lib/types';
|
||||
import {
|
||||
formatPercentage, formatProgress, formatAcademicYear, isProposedToClose, ofstedLegacyAreas, isSpecialSchool,
|
||||
formatPercentage, formatProgress, formatAcademicYear, formatAgeRange, isProposedToClose, ofstedLegacyAreas, isSpecialSchool,
|
||||
} from '@/lib/utils';
|
||||
import { DeltaChip } from './DeltaChip';
|
||||
import { SpecialSchoolNote } from './SpecialSchoolNote';
|
||||
@@ -68,15 +68,13 @@ interface SchoolDetailViewProps {
|
||||
census: SchoolCensus | null;
|
||||
admissions: SchoolAdmissions | null;
|
||||
admissionsHistory: SchoolAdmissions[];
|
||||
senDetail: SenDetail | null;
|
||||
phonics: Phonics | null;
|
||||
deprivation: SchoolDeprivation | null;
|
||||
finance: SchoolFinance | null;
|
||||
}
|
||||
|
||||
export function SchoolDetailView({
|
||||
schoolInfo, yearlyData, absenceData,
|
||||
ofsted, census, admissions, admissionsHistory, senDetail, phonics, deprivation, finance,
|
||||
ofsted, census, admissions, admissionsHistory, deprivation, finance,
|
||||
}: SchoolDetailViewProps) {
|
||||
const router = useRouter();
|
||||
const { addSchool, removeSchool, isSelected } = useComparison();
|
||||
@@ -161,14 +159,12 @@ export function SchoolDetailView({
|
||||
|
||||
// Phase detection. All-through schools cover BOTH key stages, so they are
|
||||
// neither "pure primary" nor "pure secondary": isSecondary stays true (they
|
||||
// have KS4 data) but isAllThrough gates the primary-only content (phonics,
|
||||
// have KS4 data) but isAllThrough gates the primary-only content (KS2 SATs,
|
||||
// KS2 trend) back on and switches phase-specific copy to an all-ages framing.
|
||||
const phase = schoolInfo.phase ?? '';
|
||||
const isAllThrough = phase.toLowerCase() === 'all-through';
|
||||
const isSecondary = phase.toLowerCase().includes('secondary') || isAllThrough;
|
||||
const isPrimary = !isSecondary;
|
||||
// Primary-stage content shows for pure-primary AND all-through schools.
|
||||
const showPrimaryContent = isPrimary || isAllThrough;
|
||||
|
||||
// National averages (fetched dynamically so they stay current)
|
||||
const [nationalAvg, setNationalAvg] = useState<NationalAverages | null>(null);
|
||||
@@ -220,11 +216,9 @@ export function SchoolDetailView({
|
||||
const hasInclusionData = (latestResults?.disadvantaged_pct != null)
|
||||
|| (latestResults?.eal_pct != null)
|
||||
|| (latestResults?.sen_support_pct != null)
|
||||
|| senDetail != null
|
||||
|| hasGenderSplit;
|
||||
|
||||
const hasSchoolLife = absenceData != null || census?.class_size_avg != null;
|
||||
const hasPhonics = phonics != null && phonics.year1_phonics_pct != null;
|
||||
const hasSchoolLife = absenceData != null;
|
||||
const hasDeprivation = deprivation != null && deprivation.idaci_decile != null;
|
||||
const hasFinance = finance != null && finance.per_pupil_spend != null;
|
||||
const hasLocation = schoolInfo.latitude != null && schoolInfo.longitude != null;
|
||||
@@ -265,7 +259,6 @@ export function SchoolDetailView({
|
||||
if (admissions) navItems.push({ id: 'admissions', label: 'Admissions' });
|
||||
if (hasInclusionData) navItems.push({ id: 'inclusion', label: 'Pupils' });
|
||||
if (yearlyData.length > 0) navItems.push({ id: 'history', label: 'History' });
|
||||
if (hasPhonics && showPrimaryContent) navItems.push({ id: 'phonics', label: 'Phonics' });
|
||||
if (hasSchoolLife) navItems.push({ id: 'school-life', label: 'School Life' });
|
||||
if (hasDeprivation) navItems.push({ id: 'local-area', label: 'Local Area' });
|
||||
if (hasFinance) navItems.push({ id: 'finances', label: 'Finances' });
|
||||
@@ -358,6 +351,19 @@ export function SchoolDetailView({
|
||||
{schoolInfo.gender && schoolInfo.gender !== 'Mixed' && (
|
||||
<span className={styles.metaItem}>{schoolInfo.gender}'s school</span>
|
||||
)}
|
||||
{schoolInfo.age_range && (
|
||||
<span className={styles.metaItem}>{formatAgeRange(schoolInfo.age_range)}</span>
|
||||
)}
|
||||
{schoolInfo.religious_denomination
|
||||
&& !['Does not apply', 'None'].includes(schoolInfo.religious_denomination) && (
|
||||
<span className={styles.metaItem}>{schoolInfo.religious_denomination}</span>
|
||||
)}
|
||||
{schoolInfo.nursery_provision && (
|
||||
<span className={styles.metaItem}>Nursery</span>
|
||||
)}
|
||||
{schoolInfo.has_sixth_form && (
|
||||
<span className={styles.metaItem}>Sixth form</span>
|
||||
)}
|
||||
</div>
|
||||
{isProposedToClose(schoolInfo) && (
|
||||
<div className={styles.closingStrip} role="note">
|
||||
@@ -416,6 +422,24 @@ export function SchoolDetailView({
|
||||
Part of <strong>{schoolInfo.trust_name}</strong>
|
||||
</span>
|
||||
)}
|
||||
{schoolInfo.telephone && (
|
||||
<span className={styles.headerDetail}>
|
||||
<strong>Phone:</strong>{' '}
|
||||
<a href={`tel:${schoolInfo.telephone.replace(/\s+/g, '')}`}>
|
||||
{schoolInfo.telephone}
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
{schoolInfo.county && (
|
||||
<span className={styles.headerDetail}>
|
||||
<strong>County:</strong> {schoolInfo.county}
|
||||
</span>
|
||||
)}
|
||||
{schoolInfo.parliamentary_constituency && (
|
||||
<span className={styles.headerDetail}>
|
||||
<strong>Constituency:</strong> {schoolInfo.parliamentary_constituency}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.actions} ref={heroActionsRef}>
|
||||
@@ -1084,29 +1108,6 @@ export function SchoolDetailView({
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
{senDetail && (
|
||||
<>
|
||||
<h3 className={styles.subSectionTitle}>Types of additional needs supported</h3>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
What proportion of pupils with additional needs have each type of support need.
|
||||
</p>
|
||||
<div className={styles.metricsGrid}>
|
||||
{[
|
||||
{ label: 'Speech & Language', pct: senDetail.primary_need_speech_pct },
|
||||
{ label: 'Autism (ASD)', pct: senDetail.primary_need_autism_pct },
|
||||
{ label: 'Learning Difficulties', pct: senDetail.primary_need_mld_pct },
|
||||
{ label: 'Specific Learning (e.g. Dyslexia)', pct: senDetail.primary_need_spld_pct },
|
||||
{ label: 'Social, Emotional & Mental Health', pct: senDetail.primary_need_semh_pct },
|
||||
{ label: 'Physical / Sensory', pct: senDetail.primary_need_physical_pct },
|
||||
].filter(n => n.pct != null).map(({ label, pct }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={styles.metricValue}>{pct}%</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -1231,41 +1232,11 @@ export function SchoolDetailView({
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
{/* Year 1 Phonics — primary-stage metric (pure primary + all-through) */}
|
||||
{hasPhonics && showPrimaryContent && phonics && (
|
||||
<section id="phonics" className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Year 1 Phonics ({formatAcademicYear(phonics.year)})</h2>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
Phonics is a key early reading skill. Children are tested at the end of Year 1.
|
||||
</p>
|
||||
<div className={styles.metricsGrid}>
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Passed the phonics check</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(phonics.year1_phonics_pct)}</div>
|
||||
<div className={styles.metricHint}>Phonics is a key early reading skill tested at end of Year 1</div>
|
||||
</div>
|
||||
{phonics.year2_phonics_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Year 2 pupils who retook and passed</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(phonics.year2_phonics_pct)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* School Life */}
|
||||
{hasSchoolLife && (
|
||||
<section id="school-life" className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>School Life</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{census?.class_size_avg != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Average class size</div>
|
||||
<div className={styles.metricValue}>{census.class_size_avg.toFixed(1)}</div>
|
||||
<div className={styles.metricHint}>Average number of pupils per class</div>
|
||||
</div>
|
||||
)}
|
||||
{absenceData?.overall_absence_rate != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
|
||||
@@ -20,7 +20,7 @@ const PerformanceChart = dynamic(
|
||||
import type {
|
||||
School, SchoolResult, AbsenceData,
|
||||
OfstedInspection, SchoolCensus,
|
||||
SchoolAdmissions, SenDetail, Phonics,
|
||||
SchoolAdmissions,
|
||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||
} from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, formatAcademicYear, formatAgeRange, isProposedToClose, ofstedLegacyAreas, isSpecialSchool } from '@/lib/utils';
|
||||
@@ -68,15 +68,13 @@ interface SecondarySchoolDetailViewProps {
|
||||
ofsted: OfstedInspection | null;
|
||||
census: SchoolCensus | null;
|
||||
admissions: SchoolAdmissions | null;
|
||||
senDetail: SenDetail | null;
|
||||
phonics: Phonics | null;
|
||||
deprivation: SchoolDeprivation | null;
|
||||
finance: SchoolFinance | null;
|
||||
}
|
||||
|
||||
export function SecondarySchoolDetailView({
|
||||
schoolInfo, yearlyData,
|
||||
ofsted, census, admissions, senDetail, deprivation, finance, absenceData,
|
||||
ofsted, census, admissions, deprivation, finance, absenceData,
|
||||
}: SecondarySchoolDetailViewProps) {
|
||||
const router = useRouter();
|
||||
// Hero map — the "View on map" link opens its fullscreen view.
|
||||
@@ -248,6 +246,13 @@ export function SecondarySchoolDetailView({
|
||||
{schoolInfo.age_range && (
|
||||
<span className={styles.badge}>{formatAgeRange(schoolInfo.age_range)}</span>
|
||||
)}
|
||||
{schoolInfo.religious_denomination
|
||||
&& !['Does not apply', 'None'].includes(schoolInfo.religious_denomination) && (
|
||||
<span className={styles.badge}>{schoolInfo.religious_denomination}</span>
|
||||
)}
|
||||
{schoolInfo.nursery_provision && (
|
||||
<span className={styles.badge}>Nursery</span>
|
||||
)}
|
||||
{hasSixthForm && (
|
||||
<span className={styles.badge}>Sixth form</span>
|
||||
)}
|
||||
@@ -310,6 +315,24 @@ export function SecondarySchoolDetailView({
|
||||
Part of <strong>{schoolInfo.trust_name}</strong>
|
||||
</span>
|
||||
)}
|
||||
{schoolInfo.telephone && (
|
||||
<span className={styles.headerDetail}>
|
||||
<strong>Phone:</strong>{' '}
|
||||
<a href={`tel:${schoolInfo.telephone.replace(/\s+/g, '')}`}>
|
||||
{schoolInfo.telephone}
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
{schoolInfo.county && (
|
||||
<span className={styles.headerDetail}>
|
||||
<strong>County:</strong> {schoolInfo.county}
|
||||
</span>
|
||||
)}
|
||||
{schoolInfo.parliamentary_constituency && (
|
||||
<span className={styles.headerDetail}>
|
||||
<strong>Constituency:</strong> {schoolInfo.parliamentary_constituency}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
|
||||
Reference in New Issue
Block a user