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}
/>
);