fix(compare): prevent auto-phase from overriding manual tab selection
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 50s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s

Once the user explicitly clicks a phase tab, suppress auto-phase detection
so switching to Secondary (or Primary) can't be snapped back by the effect
that fires when comparisonData re-fetches.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tudor Sitaru
2026-04-16 09:11:47 +01:00
parent 2e3456b21b
commit 8154a59014
+6 -1
View File
@@ -5,7 +5,7 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { useRouter, usePathname, useSearchParams } from 'next/navigation';
import { useComparison } from '@/hooks/useComparison'; import { useComparison } from '@/hooks/useComparison';
import { ComparisonChart } from './ComparisonChart'; import { ComparisonChart } from './ComparisonChart';
@@ -59,6 +59,9 @@ export function ComparisonView({
const [comparisonData, setComparisonData] = useState(initialData); const [comparisonData, setComparisonData] = useState(initialData);
const [shareConfirm, setShareConfirm] = useState(false); const [shareConfirm, setShareConfirm] = useState(false);
const [comparePhase, setComparePhase] = useState<'primary' | 'secondary'>('primary'); const [comparePhase, setComparePhase] = useState<'primary' | 'secondary'>('primary');
// Tracks whether the user has explicitly clicked a phase tab.
// While true, auto-phase detection is suppressed so manual selections aren't overridden.
const phaseLockedByUser = useRef(false);
// Seed context from initialData when component mounts and localStorage is empty // Seed context from initialData when component mounts and localStorage is empty
useEffect(() => { useEffect(() => {
@@ -124,6 +127,7 @@ export function ComparisonView({
// needs to follow, otherwise all secondary cards show "" for a primary-only field. // needs to follow, otherwise all secondary cards show "" for a primary-only field.
useEffect(() => { useEffect(() => {
if (!comparisonData || selectedSchools.length === 0) return; if (!comparisonData || selectedSchools.length === 0) return;
if (phaseLockedByUser.current) return;
const newPhase = secondarySchools.length > primarySchools.length ? 'secondary' : 'primary'; const newPhase = secondarySchools.length > primarySchools.length ? 'secondary' : 'primary';
setComparePhase(newPhase); setComparePhase(newPhase);
// Only reset the metric when it doesn't belong to the newly detected phase. // Only reset the metric when it doesn't belong to the newly detected phase.
@@ -138,6 +142,7 @@ export function ComparisonView({
}, [comparisonData]); // eslint-disable-line react-hooks/exhaustive-deps }, [comparisonData]); // eslint-disable-line react-hooks/exhaustive-deps
const handlePhaseChange = (phase: 'primary' | 'secondary') => { const handlePhaseChange = (phase: 'primary' | 'secondary') => {
phaseLockedByUser.current = true;
setComparePhase(phase); setComparePhase(phase);
const defaultMetric = phase === 'secondary' ? 'attainment_8_score' : 'rwm_expected_pct'; const defaultMetric = phase === 'secondary' ? 'attainment_8_score' : 'rwm_expected_pct';
setSelectedMetric(defaultMetric); setSelectedMetric(defaultMetric);