fix(map): add effect deps, escape HTML in popup, document Att8 delta threshold
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 24s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 53s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s

This commit is contained in:
Tudor Sitaru
2026-04-13 14:32:55 +01:00
parent 177571f411
commit 9c50c49e1f
+10 -4
View File
@@ -33,6 +33,10 @@ interface LeafletMapInnerProps {
// Popup helpers (must work in plain JS string templates — no React / CSS Modules) // Popup helpers (must work in plain JS string templates — no React / CSS Modules)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function escapeHtml(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
interface PopupBadge { interface PopupBadge {
label: string; label: string;
style: string; style: string;
@@ -135,8 +139,10 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
if (laAvg != null) { if (laAvg != null) {
const diff = Math.round((score - laAvg) * 10) / 10; const diff = Math.round((score - laAvg) * 10) / 10;
const sign = diff >= 0 ? '+' : ''; const sign = diff >= 0 ? '+' : '';
// Att8 scores range 090 in 0.1 increments; ±0.5 is meaningful here
// vs primary RWM % where ±2 pts is the threshold
const colour = diff >= 0.5 ? '#2d7d7d' : diff <= -0.5 ? '#e07256' : '#8a847a'; const colour = diff >= 0.5 ? '#2d7d7d' : diff <= -0.5 ? '#e07256' : '#8a847a';
const laName = school.local_authority ?? 'LA'; const laName = escapeHtml(school.local_authority ?? 'LA');
deltaLine = `<div style="font-size:11px;font-weight:600;color:${colour}">${sign}${diff} vs ${laName} avg</div>`; deltaLine = `<div style="font-size:11px;font-weight:600;color:${colour}">${sign}${diff} vs ${laName} avg</div>`;
} }
metricHtml = `<div style="margin-bottom:4px"> metricHtml = `<div style="margin-bottom:4px">
@@ -167,11 +173,11 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
const popupContent = `<div style="font-family:system-ui,sans-serif;min-width:240px;max-width:280px;padding:0"> const popupContent = `<div style="font-family:system-ui,sans-serif;min-width:240px;max-width:280px;padding:0">
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:6px"> <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:6px">
<strong style="font-size:13px;color:#1a1612;line-height:1.3">${school.school_name}</strong> <strong style="font-size:13px;color:#1a1612;line-height:1.3">${escapeHtml(school.school_name)}</strong>
<span style="font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;white-space:nowrap;flex-shrink:0;${badge.style}">${badge.label}</span> <span style="font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;white-space:nowrap;flex-shrink:0;${badge.style}">${badge.label}</span>
</div> </div>
<div style="font-size:11px;color:#8a847a;margin-bottom:8px"> <div style="font-size:11px;color:#8a847a;margin-bottom:8px">
${phaseLabel}${school.local_authority ? ` · ${school.local_authority}` : ''}${distanceStr} ${phaseLabel}${school.local_authority ? ` · ${escapeHtml(school.local_authority)}` : ''}${distanceStr}
</div> </div>
${metricHtml} ${metricHtml}
<a href="${slug}" style="display:block;text-align:center;padding:6px;background:#2d7d7d;color:white;border-radius:5px;text-decoration:none;font-size:12px;font-weight:600;margin-top:8px">View Details →</a> <a href="${slug}" style="display:block;text-align:center;padding:6px;background:#2d7d7d;color:white;border-radius:5px;text-decoration:none;font-size:12px;font-weight:600;margin-top:8px">View Details →</a>
@@ -201,7 +207,7 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
return () => { return () => {
// Don't destroy map on every update, just clean markers // Don't destroy map on every update, just clean markers
}; };
}, [schools, center, zoom, referencePoint, onMarkerClick]); }, [schools, center, zoom, referencePoint, onMarkerClick, nationalAvgRwm, laAverages]);
// Cleanup map on unmount // Cleanup map on unmount
useEffect(() => { useEffect(() => {