diff --git a/nextjs-app/components/HomeView.tsx b/nextjs-app/components/HomeView.tsx index 3c2a4d9..91b4aad 100644 --- a/nextjs-app/components/HomeView.tsx +++ b/nextjs-app/components/HomeView.tsx @@ -261,6 +261,7 @@ export function HomeView({ initialSchools, filters, totalSchools }: HomeViewProp referencePoint={initialSchools.location_info?.coordinates} onMarkerClick={setSelectedMapSchool} nationalAvgRwm={nationalAvgRwm} + laAverages={laAverages} />
diff --git a/nextjs-app/components/LeafletMapInner.tsx b/nextjs-app/components/LeafletMapInner.tsx index e5bae70..cc7e2f6 100644 --- a/nextjs-app/components/LeafletMapInner.tsx +++ b/nextjs-app/components/LeafletMapInner.tsx @@ -25,9 +25,39 @@ interface LeafletMapInnerProps { zoom: number; referencePoint?: [number, number]; onMarkerClick?: (school: School) => void; + nationalAvgRwm?: number | null; + laAverages?: Record; } -export default function LeafletMapInner({ schools, center, zoom, referencePoint, onMarkerClick }: LeafletMapInnerProps) { +// --------------------------------------------------------------------------- +// Popup helpers (must work in plain JS string templates — no React / CSS Modules) +// --------------------------------------------------------------------------- + +interface PopupBadge { + label: string; + style: string; +} + +function buildPopupBadge(school: School): PopupBadge { + const year = school.ofsted_date ? new Date(school.ofsted_date).getFullYear() : null; + const yearStr = year ? ` · ${year}` : ''; + if (school.ofsted_grade) { + const labels: Record = { 1: 'Outstanding', 2: 'Good', 3: 'Req. Improvement', 4: 'Inadequate' }; + const colours: Record = { + 1: 'background:#d4f0ea;color:#2d7d7d', + 2: 'background:rgba(60,140,60,0.12);color:#3c8c3c', + 3: 'background:#fef3cd;color:#b8920e', + 4: 'background:#fde8e0;color:#e07256', + }; + return { label: `${labels[school.ofsted_grade]}${yearStr}`, style: colours[school.ofsted_grade] }; + } + if (school.ofsted_framework === 'ReportCard') { + return { label: `Report Card${yearStr}`, style: 'background:#5a3a6e;color:#fff' }; + } + return { label: 'Not yet inspected', style: 'background:#e0e0e0;color:#666' }; +} + +export default function LeafletMapInner({ schools, center, zoom, referencePoint, onMarkerClick, nationalAvgRwm, laAverages }: LeafletMapInnerProps) { const mapRef = useRef(null); const mapContainerRef = useRef(null); const refMarkerRef = useRef(null); @@ -81,14 +111,71 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint, const marker = L.marker([school.latitude, school.longitude]).addTo(mapRef.current); // Create popup content - const popupContent = ` -
- ${school.school_name} - ${school.local_authority ? `
${school.local_authority}
` : ''} - ${school.school_type ? `
${school.school_type}
` : ''} - View Details -
- `; + const badge = buildPopupBadge(school); + const isSecondary = school.attainment_8_score != null; + + // Phase label + const rawPhase = (school.phase ?? '').toLowerCase(); + const phaseLabel = + rawPhase.includes('secondary') ? 'Secondary' : + rawPhase === 'all-through' ? 'All-through' : + rawPhase.includes('primary') ? 'Primary' : + isSecondary ? 'Secondary' : 'Primary'; + + // Distance string + const distanceStr = + school.distance != null ? ` · ${school.distance.toFixed(1)} mi` : ''; + + // Headline metric + let metricHtml = ''; + if (isSecondary) { + const score = school.attainment_8_score!; + const laAvg = school.local_authority ? (laAverages?.[school.local_authority] ?? null) : null; + let deltaLine = ''; + if (laAvg != null) { + const diff = Math.round((score - laAvg) * 10) / 10; + const sign = diff >= 0 ? '+' : ''; + const colour = diff >= 0.5 ? '#2d7d7d' : diff <= -0.5 ? '#e07256' : '#8a847a'; + const laName = school.local_authority ?? 'LA'; + deltaLine = `
${sign}${diff} vs ${laName} avg
`; + } + metricHtml = `
+ ${score.toFixed(1)} + Attainment 8 + ${deltaLine} +
`; + } else if (school.rwm_expected_pct != null) { + const rwm = school.rwm_expected_pct; + let deltaLine = ''; + if (nationalAvgRwm != null) { + const diff = Math.round(rwm - nationalAvgRwm); + const colour = diff >= 2 ? '#2d7d7d' : diff <= -2 ? '#e07256' : '#8a847a'; + const text = + diff >= 2 ? `+${diff} pts vs national` : + diff <= -2 ? `${diff} pts vs national` : + '≈ national avg'; + deltaLine = `
${text}
`; + } + metricHtml = `
+ ${rwm}% + Reading, Writing & Maths + ${deltaLine} +
`; + } + + const slug = schoolUrl(school.urn, school.school_name); + + const popupContent = `
+
+ ${school.school_name} + ${badge.label} +
+
+ ${phaseLabel}${school.local_authority ? ` · ${school.local_authority}` : ''}${distanceStr} +
+ ${metricHtml} + View Details → +
`; marker.bindPopup(popupContent); diff --git a/nextjs-app/components/SchoolMap.tsx b/nextjs-app/components/SchoolMap.tsx index c1e09f1..4cd8119 100644 --- a/nextjs-app/components/SchoolMap.tsx +++ b/nextjs-app/components/SchoolMap.tsx @@ -28,10 +28,10 @@ interface SchoolMapProps { referencePoint?: [number, number]; onMarkerClick?: (school: School) => void; nationalAvgRwm?: number | null; + laAverages?: Record; } -export function SchoolMap({ schools, center, zoom = 13, referencePoint, onMarkerClick, nationalAvgRwm: _nationalAvgRwm }: SchoolMapProps) { - // TODO: thread _nationalAvgRwm to LeafletMapInner (Task 7) +export function SchoolMap({ schools, center, zoom = 13, referencePoint, onMarkerClick, nationalAvgRwm, laAverages }: SchoolMapProps) { const wrapperRef = useRef(null); const [isFullscreen, setIsFullscreen] = useState(false); @@ -91,6 +91,8 @@ export function SchoolMap({ schools, center, zoom = 13, referencePoint, onMarker zoom={zoom} referencePoint={referencePoint} onMarkerClick={onMarkerClick} + nationalAvgRwm={nationalAvgRwm} + laAverages={laAverages} />
);