import { useState, useMemo } from 'react' import { useRoutePokemon } from '../hooks/useGames' import { EncounterMethodBadge, getMethodLabel, METHOD_ORDER } from './EncounterMethodBadge' import type { Route, RouteEncounterDetail } from '../types' interface ShinyEncounterModalProps { routes: Route[] gameId: number onSubmit: (data: { routeId: number pokemonId: number nickname?: string | undefined status: 'caught' catchLevel?: number | undefined isShiny: true }) => void onClose: () => void isPending: boolean } const SPECIAL_METHODS = ['starter', 'gift', 'fossil', 'trade'] function groupByMethod( pokemon: RouteEncounterDetail[] ): { method: string; pokemon: RouteEncounterDetail[] }[] { const groups = new Map() for (const rp of pokemon) { const list = groups.get(rp.encounterMethod) ?? [] list.push(rp) groups.set(rp.encounterMethod, list) } return [...groups.entries()] .sort(([a], [b]) => { const ai = METHOD_ORDER.indexOf(a) const bi = METHOD_ORDER.indexOf(b) return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi) }) .map(([method, pokemon]) => ({ method, pokemon })) } export function ShinyEncounterModal({ routes, gameId, onSubmit, onClose, isPending, }: ShinyEncounterModalProps) { const [selectedRouteId, setSelectedRouteId] = useState(null) const { data: routePokemon, isLoading: loadingPokemon } = useRoutePokemon(selectedRouteId, gameId) const [selectedPokemon, setSelectedPokemon] = useState(null) const [nickname, setNickname] = useState('') const [catchLevel, setCatchLevel] = useState('') const [search, setSearch] = useState('') const filteredPokemon = routePokemon?.filter((rp) => rp.pokemon.name.toLowerCase().includes(search.toLowerCase()) ) const groupedPokemon = useMemo( () => (filteredPokemon ? groupByMethod(filteredPokemon) : []), [filteredPokemon] ) const hasMultipleGroups = groupedPokemon.length > 1 // Reset selection when route changes const handleRouteChange = (routeId: number) => { setSelectedRouteId(routeId) setSelectedPokemon(null) setSearch('') } const handleSubmit = () => { if (selectedPokemon && selectedRouteId) { onSubmit({ routeId: selectedRouteId, pokemonId: selectedPokemon.pokemonId, nickname: nickname || undefined, status: 'caught', catchLevel: catchLevel ? Number(catchLevel) : undefined, isShiny: true, }) } } // Only show leaf routes (no children, i.e. routes that aren't parents) const parentIds = new Set( routes.filter((r) => r.parentRouteId !== null).map((r) => r.parentRouteId) ) const leafRoutes = routes.filter((r) => !parentIds.has(r.id)) return (

Log Shiny Encounter

Shiny catches bypass the one-per-route rule

{/* Route selector */}
{/* Pokemon Selection */} {selectedRouteId && (
{loadingPokemon ? (
) : filteredPokemon && filteredPokemon.length > 0 ? ( <> {(routePokemon?.length ?? 0) > 6 && ( setSearch(e.target.value)} className="w-full px-3 py-1.5 mb-2 rounded-lg border border-border-default bg-surface-2 text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-yellow-500" /> )}
{groupedPokemon.map(({ method, pokemon }, groupIdx) => (
{groupIdx > 0 &&
} {hasMultipleGroups && (
{getMethodLabel(method)}
)}
{pokemon.map((rp) => ( ))}
))}
) : (

No pokemon data for this route

)}
)} {/* Nickname */} {selectedPokemon && (
setNickname(e.target.value)} placeholder="Give it a name..." className="w-full px-3 py-2 rounded-lg border border-border-default bg-surface-2 text-text-primary focus:outline-none focus:ring-2 focus:ring-yellow-500" />
)} {/* Catch Level */} {selectedPokemon && (
setCatchLevel(e.target.value)} placeholder={ selectedPokemon ? `${selectedPokemon.minLevel}–${selectedPokemon.maxLevel}` : 'Level' } className="w-24 px-3 py-2 rounded-lg border border-border-default bg-surface-2 text-text-primary focus:outline-none focus:ring-2 focus:ring-yellow-500" />
)}
) }