From 6d955439eba89a7a97deccbc108dde902e683a05 Mon Sep 17 00:00:00 2001 From: Julian Tabel Date: Mon, 9 Feb 2026 12:21:07 +0100 Subject: [PATCH] Fix team sort: add to RunEncounters and fix hook ordering Add sort dropdown to RunEncounters (the encounters page with the expandable team section) and move all useMemo hooks before early returns in both RunDashboard and RunEncounters to fix React hook ordering violations. Co-Authored-By: Claude Opus 4.6 --- frontend/src/pages/RunDashboard.tsx | 18 +++-- frontend/src/pages/RunEncounters.tsx | 105 +++++++++++++++++++-------- 2 files changed, 85 insertions(+), 38 deletions(-) diff --git a/frontend/src/pages/RunDashboard.tsx b/frontend/src/pages/RunDashboard.tsx index 2d5472f..fcda00b 100644 --- a/frontend/src/pages/RunDashboard.tsx +++ b/frontend/src/pages/RunDashboard.tsx @@ -56,6 +56,16 @@ export function RunDashboard() { const [showEndRun, setShowEndRun] = useState(false) const [teamSort, setTeamSort] = useState('route') + const encounters = run?.encounters ?? [] + const alive = useMemo( + () => sortEncounters(encounters.filter((e) => e.status === 'caught' && e.faintLevel === null), teamSort), + [encounters, teamSort], + ) + const dead = useMemo( + () => sortEncounters(encounters.filter((e) => e.status === 'caught' && e.faintLevel !== null), teamSort), + [encounters, teamSort], + ) + if (isLoading) { return (
@@ -81,14 +91,6 @@ export function RunDashboard() { } const isActive = run.status === 'active' - const alive = useMemo( - () => sortEncounters(run.encounters.filter((e) => e.status === 'caught' && e.faintLevel === null), teamSort), - [run.encounters, teamSort], - ) - const dead = useMemo( - () => sortEncounters(run.encounters.filter((e) => e.status === 'caught' && e.faintLevel !== null), teamSort), - [run.encounters, teamSort], - ) const visitedRoutes = new Set(run.encounters.map((e) => e.routeId)).size const totalRoutes = routes?.length diff --git a/frontend/src/pages/RunEncounters.tsx b/frontend/src/pages/RunEncounters.tsx index fa9e324..ec50e65 100644 --- a/frontend/src/pages/RunEncounters.tsx +++ b/frontend/src/pages/RunEncounters.tsx @@ -33,6 +33,28 @@ import type { BossPokemon, } from '../types' +type TeamSortKey = 'route' | 'level' | 'species' | 'dex' + +function sortEncounters(encounters: EncounterDetail[], key: TeamSortKey): EncounterDetail[] { + return [...encounters].sort((a, b) => { + switch (key) { + case 'route': + return a.route.order - b.route.order + case 'level': + return (a.catchLevel ?? 0) - (b.catchLevel ?? 0) + case 'species': { + const nameA = (a.currentPokemon ?? a.pokemon).name + const nameB = (b.currentPokemon ?? b.pokemon).name + return nameA.localeCompare(nameB) + } + case 'dex': + return (a.currentPokemon ?? a.pokemon).nationalDex - (b.currentPokemon ?? b.pokemon).nationalDex + default: + return 0 + } + }) +} + const statusStyles: Record = { active: 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300', completed: @@ -421,6 +443,7 @@ export function RunEncounters() { const [showEggModal, setShowEggModal] = useState(false) const [expandedBosses, setExpandedBosses] = useState>(new Set()) const [showTeam, setShowTeam] = useState(true) + const [teamSort, setTeamSort] = useState('route') const [filter, setFilter] = useState<'all' | RouteStatus>('all') const storageKey = `expandedGroups-${runId}` @@ -621,10 +644,21 @@ export function RunEncounters() { }, [organizedRoutes, encounterByRoute]) // eslint-disable-line react-hooks/exhaustive-deps const alive = useMemo( - () => [...normalEncounters, ...transferEncounters, ...shinyEncounters].filter( - (e) => e.status === 'caught' && e.faintLevel === null, + () => sortEncounters( + [...normalEncounters, ...transferEncounters, ...shinyEncounters].filter( + (e) => e.status === 'caught' && e.faintLevel === null, + ), + teamSort, ), - [normalEncounters, transferEncounters, shinyEncounters], + [normalEncounters, transferEncounters, shinyEncounters, teamSort], + ) + + const dead = useMemo( + () => sortEncounters( + normalEncounters.filter((e) => e.status === 'caught' && e.faintLevel !== null), + teamSort, + ), + [normalEncounters, teamSort], ) // Resolve HoF team encounters from IDs @@ -686,9 +720,6 @@ export function RunEncounters() { } const isActive = run.status === 'active' - const dead = normalEncounters.filter( - (e) => e.status === 'caught' && e.faintLevel !== null, - ) const toggleGroup = (groupId: number) => { updateExpandedGroups((prev) => { @@ -1036,31 +1067,45 @@ export function RunEncounters() { {/* Team Section */} {(alive.length > 0 || dead.length > 0) && (
- +

+ {isActive ? 'Team' : 'Final Team'} +

+ + {alive.length} alive{dead.length > 0 ? `, ${dead.length} dead` : ''} + + + + + + {showTeam && alive.length > 1 && ( + + )} +
{showTeam && ( <> {alive.length > 0 && (