refactor(detail): extract derived flags and nav items to lib
Pure data-shape logic moves out of the client components so page.tsx can compute the section list on the server without importing them. The secondary page is not a variant of the primary one -- different section ids (gcse, wellbeing), different flags, and History gated on more than one year rather than at least one -- so it gets its own computeSecondaryFlags and buildSecondaryNavItems rather than bending a shared function. Adds 16 unit tests covering the all-through and special-school branches, previously reachable only through a full component render. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,8 @@ import type {
|
||||
SchoolAdmissions,
|
||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||
} from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, formatAcademicYear, formatAgeRange, isProposedToClose, ofstedLegacyAreas, isSpecialSchool } from '@/lib/utils';
|
||||
import { formatPercentage, formatProgress, formatAcademicYear, formatAgeRange, isProposedToClose, ofstedLegacyAreas } from '@/lib/utils';
|
||||
import { computeSecondaryFlags, buildSecondaryNavItems } from '@/lib/schoolSections';
|
||||
import { DeltaChip } from './DeltaChip';
|
||||
import { SpecialSchoolNote } from './SpecialSchoolNote';
|
||||
import { track, getNavigationSource } from '@/lib/analytics';
|
||||
@@ -90,29 +91,16 @@ export function SecondarySchoolDetailView({
|
||||
// Header details collapse behind a "Show all details" link on mobile/tablet.
|
||||
const [detailsOpen, setDetailsOpen] = useState(false);
|
||||
|
||||
const latestResults = yearlyData.length > 0 ? yearlyData[yearlyData.length - 1] : null;
|
||||
// Derived data-shape logic lives in lib/schoolSections so the server route
|
||||
// can compute the section list without importing this client component.
|
||||
const flags = computeSecondaryFlags({ schoolInfo, yearlyData, deprivation, finance });
|
||||
const {
|
||||
latestResults, hasSixthForm, hasFinance, hasDeprivation, hasLocation,
|
||||
hasWellbeing, hasResults, p8Suspended, isSpecial, suppressComparison,
|
||||
} = flags;
|
||||
|
||||
const secondaryAvg = nationalAvg?.secondary ?? {};
|
||||
|
||||
// GIAS OfficialSixthForm flag; missing (pipeline not yet re-run) => false.
|
||||
const hasSixthForm = schoolInfo.has_sixth_form ?? false;
|
||||
const hasFinance = finance != null && finance.per_pupil_spend != null;
|
||||
const hasDeprivation = deprivation != null && deprivation.idaci_decile != null;
|
||||
const hasLocation = schoolInfo.latitude != null && schoolInfo.longitude != null;
|
||||
const hasWellbeing = (latestResults?.sen_support_pct != null || latestResults?.sen_ehcp_pct != null) || hasDeprivation;
|
||||
const p8Suspended = latestResults != null && latestResults.year >= 202425;
|
||||
const hasResults = latestResults?.attainment_8_score != null;
|
||||
|
||||
// Special schools / PRUs / AP sit the same GCSEs but teach pupils with SEND,
|
||||
// so their headline attainment is far below the mainstream average by design.
|
||||
// Drop the England comparison + "below" framing so the page doesn't portray
|
||||
// them as failing against a benchmark that doesn't fit. Attainment 8 is a
|
||||
// single 0–80 score with no subject breakdown to test for a placeholder, so
|
||||
// this keys off establishment type only — a genuine (if extreme) 0.0 at a
|
||||
// mainstream school still shows its real value and comparison.
|
||||
const isSpecial = isSpecialSchool(schoolInfo);
|
||||
const suppressComparison = isSpecial;
|
||||
|
||||
const admissionsTag = (() => {
|
||||
const policy = schoolInfo.admissions_policy?.toLowerCase() ?? '';
|
||||
if (policy.includes('selective')) return 'Selective';
|
||||
@@ -155,17 +143,9 @@ export function SecondarySchoolDetailView({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [schoolInfo.urn]);
|
||||
|
||||
// Build nav items dynamically based on available data.
|
||||
// Engagement-led order (matches the primary page): recognised Ofsted badge,
|
||||
// then the most-sought sections — results, admissions, history — with the
|
||||
// experience and context sections following.
|
||||
const navItems: { id: string; label: string }[] = [];
|
||||
if (ofsted) navItems.push({ id: 'ofsted', label: 'Ofsted' });
|
||||
if (hasResults) navItems.push({ id: 'gcse', label: 'GCSEs' });
|
||||
if (admissions) navItems.push({ id: 'admissions', label: 'Admissions' });
|
||||
if (yearlyData.length > 1) navItems.push({ id: 'history', label: 'History' });
|
||||
if (hasWellbeing) navItems.push({ id: 'wellbeing', label: 'Wellbeing' });
|
||||
if (hasFinance) navItems.push({ id: 'finances', label: 'Finances' });
|
||||
const navItems = buildSecondaryNavItems(flags, {
|
||||
ofsted, admissions, yearlyDataLength: yearlyData.length,
|
||||
});
|
||||
|
||||
// Track active section as user scrolls
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user