Add nullable region field to evolutions for regional form filtering

Regional evolutions (e.g., Pikachu → Alolan Raichu) only occur in specific
regions. This adds a nullable region column so the app can filter evolutions
by the game's region. When a regional evolution exists for a given trigger/item,
the non-regional counterpart is automatically hidden.

Full-stack: migration, model, schemas, API with region query param, seeder,
Go fetch tool, frontend types/API/hook/components, and admin form.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 20:05:07 +01:00
parent 23a7b6ad53
commit a65efa22da
17 changed files with 147 additions and 11 deletions

View File

@@ -24,6 +24,7 @@ export function deleteEncounter(id: number): Promise<void> {
return api.del(`/encounters/${id}`)
}
export function fetchEvolutions(pokemonId: number): Promise<Evolution[]> {
return api.get(`/pokemon/${pokemonId}/evolutions`)
export function fetchEvolutions(pokemonId: number, region?: string): Promise<Evolution[]> {
const params = region ? `?region=${encodeURIComponent(region)}` : ''
return api.get(`/pokemon/${pokemonId}/evolutions${params}`)
}

View File

@@ -10,6 +10,7 @@ interface StatusChangeModalProps {
}) => void
onClose: () => void
isPending: boolean
region?: string
}
const typeColors: Record<string, string> = {
@@ -60,6 +61,7 @@ export function StatusChangeModal({
onUpdate,
onClose,
isPending,
region,
}: StatusChangeModalProps) {
const { pokemon, currentPokemon, route, nickname, catchLevel, faintLevel, deathCause } =
encounter
@@ -72,7 +74,8 @@ export function StatusChangeModal({
const activePokemonId = currentPokemon?.id ?? pokemon.id
const { data: evolutions, isLoading: evolutionsLoading } = useEvolutions(
showEvolve ? activePokemonId : null
showEvolve ? activePokemonId : null,
region,
)
const handleConfirmDeath = () => {

View File

@@ -29,6 +29,7 @@ export function EvolutionFormModal({
const [item, setItem] = useState(evolution?.item ?? '')
const [heldItem, setHeldItem] = useState(evolution?.heldItem ?? '')
const [condition, setCondition] = useState(evolution?.condition ?? '')
const [region, setRegion] = useState(evolution?.region ?? '')
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
@@ -41,6 +42,7 @@ export function EvolutionFormModal({
item: item || null,
heldItem: heldItem || null,
condition: condition || null,
region: region || null,
})
}
@@ -119,6 +121,16 @@ export function EvolutionFormModal({
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Region</label>
<input
type="text"
value={region}
onChange={(e) => setRegion(e.target.value)}
placeholder="e.g. alola (blank = all regions)"
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
</FormModal>
)
}

View File

@@ -43,10 +43,10 @@ export function useDeleteEncounter(runId: number) {
})
}
export function useEvolutions(pokemonId: number | null) {
export function useEvolutions(pokemonId: number | null, region?: string) {
return useQuery({
queryKey: ['evolutions', pokemonId],
queryFn: () => fetchEvolutions(pokemonId!),
queryKey: ['evolutions', pokemonId, region],
queryFn: () => fetchEvolutions(pokemonId!, region),
enabled: pokemonId !== null,
})
}

View File

@@ -242,6 +242,7 @@ export function RunDashboard() {
}}
onClose={() => setSelectedEncounter(null)}
isPending={updateEncounter.isPending}
region={run?.game.region}
/>
)}

View File

@@ -748,6 +748,7 @@ export function RunEncounters() {
}}
onClose={() => setSelectedTeamEncounter(null)}
isPending={updateEncounter.isPending}
region={run?.game.region}
/>
)}

View File

@@ -86,6 +86,7 @@ export interface EvolutionAdmin {
item: string | null
heldItem: string | null
condition: string | null
region: string | null
}
export interface PaginatedEvolutions {
@@ -103,6 +104,7 @@ export interface CreateEvolutionInput {
item?: string | null
heldItem?: string | null
condition?: string | null
region?: string | null
}
export interface UpdateEvolutionInput {
@@ -113,4 +115,5 @@ export interface UpdateEvolutionInput {
item?: string | null
heldItem?: string | null
condition?: string | null
region?: string | null
}

View File

@@ -93,6 +93,7 @@ export interface Evolution {
item: string | null
heldItem: string | null
condition: string | null
region: string | null
}
export interface CreateRunInput {