fix(school-detail): move active-section useEffect after navItems declaration
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Failing after 13s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped

TypeScript compile error: 'navItems' used before its declaration.
The IntersectionObserver useEffect referenced navItems in its dep array
but was placed above the navItems const declaration. Move it to just
after navItems is built.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tudor Sitaru
2026-04-08 15:59:01 +01:00
parent 536a166b35
commit e625addc3b
+32 -36
View File
@@ -82,42 +82,6 @@ export function SchoolDetailView({
const isSecondary = phase.toLowerCase().includes('secondary') || phase.toLowerCase() === 'all-through'; const isSecondary = phase.toLowerCase().includes('secondary') || phase.toLowerCase() === 'all-through';
const isPrimary = !isSecondary; const isPrimary = !isSecondary;
// Track active section as user scrolls
useEffect(() => {
const ids = navItems.map(n => n.id);
if (!ids.length) return;
const observers: IntersectionObserver[] = [];
// We keep a map of how much each section is visible so we can pick the
// most-visible one when multiple overlap the viewport.
const ratioMap: Record<string, number> = {};
const pickActive = () => {
const top = Object.entries(ratioMap).sort((a, b) => b[1] - a[1])[0];
setActiveSection(top?.[1] > 0 ? top[0] : '');
};
ids.forEach(id => {
const el = document.getElementById(id);
if (!el) return;
ratioMap[id] = 0;
const obs = new IntersectionObserver(
([entry]) => {
ratioMap[id] = entry.intersectionRatio;
pickActive();
},
{ threshold: [0, 0.1, 0.25, 0.5, 0.75, 1.0], rootMargin: '-56px 0px 0px 0px' },
);
obs.observe(el);
observers.push(obs);
});
return () => observers.forEach(o => o.disconnect());
// navItems identity is stable per render — eslint-disable is intentional
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [navItems.map(n => n.id).join(',')]);
// National averages (fetched dynamically so they stay current) // National averages (fetched dynamically so they stay current)
const [nationalAvg, setNationalAvg] = useState<NationalAverages | null>(null); const [nationalAvg, setNationalAvg] = useState<NationalAverages | null>(null);
useEffect(() => { useEffect(() => {
@@ -176,6 +140,38 @@ export function SchoolDetailView({
if (hasFinance) navItems.push({ id: 'finances', label: 'Finances' }); if (hasFinance) navItems.push({ id: 'finances', label: 'Finances' });
if (yearlyData.length > 0) navItems.push({ id: 'history', label: 'History' }); if (yearlyData.length > 0) navItems.push({ id: 'history', label: 'History' });
// Track active section as user scrolls
useEffect(() => {
const ids = navItems.map(n => n.id);
if (!ids.length) return;
const observers: IntersectionObserver[] = [];
const ratioMap: Record<string, number> = {};
const pickActive = () => {
const top = Object.entries(ratioMap).sort((a, b) => b[1] - a[1])[0];
setActiveSection(top?.[1] > 0 ? top[0] : '');
};
ids.forEach(id => {
const el = document.getElementById(id);
if (!el) return;
ratioMap[id] = 0;
const obs = new IntersectionObserver(
([entry]) => {
ratioMap[id] = entry.intersectionRatio;
pickActive();
},
{ threshold: [0, 0.1, 0.25, 0.5, 0.75, 1.0], rootMargin: '-56px 0px 0px 0px' },
);
obs.observe(el);
observers.push(obs);
});
return () => observers.forEach(o => o.disconnect());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [navItems.map(n => n.id).join(',')]);
// ── Ofsted: detect if all OEIF sub-grades match the overall ─────────── // ── Ofsted: detect if all OEIF sub-grades match the overall ───────────
const oeifAllSameGrade = (() => { const oeifAllSameGrade = (() => {
if (!ofsted || ofsted.framework === 'ReportCard') return false; if (!ofsted || ofsted.framework === 'ReportCard') return false;