From fa26491086013f1ef258cd0ec6e957f288716a99 Mon Sep 17 00:00:00 2001 From: joelte Date: Wed, 9 Sep 2026 22:15:33 +0200 Subject: [PATCH 1/6] feat: add grid of pokemon with images and names Added a grid of pokemon pulling the name and image from the PokeAPI api. --- PTG/src/App.tsx | 6 +++-- PTG/src/PokemonGrid.css | 27 +++++++++++++++++++ PTG/src/PokemonGrid.tsx | 60 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 PTG/src/PokemonGrid.css create mode 100644 PTG/src/PokemonGrid.tsx diff --git a/PTG/src/App.tsx b/PTG/src/App.tsx index 18749fd..15b43c0 100644 --- a/PTG/src/App.tsx +++ b/PTG/src/App.tsx @@ -1,13 +1,15 @@ import Header from "./Header.tsx" import ChoosePokemon from "./ChoosePokemon.tsx"; +import PokemonGrid from "./PokemonGrid.tsx"; export default function App(){ - + return(
+
- ); + ); } \ No newline at end of file diff --git a/PTG/src/PokemonGrid.css b/PTG/src/PokemonGrid.css new file mode 100644 index 0000000..db5d7b2 --- /dev/null +++ b/PTG/src/PokemonGrid.css @@ -0,0 +1,27 @@ +.pokemonGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 1rem; + padding: 1rem; +} + +.pokemonCard { + display: flex; + flex-direction: column; + align-items: center; + border: 1px solid #ddd; + border-radius: 8px; + padding: 0.5rem; + text-align: center; +} + +.pokemonCard img { + width: 100%; + max-width: 120px; + height: auto; +} + +.pokemonGridError { + color: red; + text-align: center; +} diff --git a/PTG/src/PokemonGrid.tsx b/PTG/src/PokemonGrid.tsx new file mode 100644 index 0000000..09528a0 --- /dev/null +++ b/PTG/src/PokemonGrid.tsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from "react"; +import "./PokemonGrid.css"; + +const POKEMON_COUNT = 100; +const LIST_URL = `https://pokeapi.co/api/v2/pokemon?limit=${POKEMON_COUNT}`; +const ARTWORK_URL = (id: number) => + `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${id}.png`; + +type Pokemon = { + id: number; + name: string; + image: string; +}; + +function capitalize(name: string) { + return name.charAt(0).toUpperCase() + name.slice(1); +} + +export default function PokemonGrid() { + const [pokemon, setPokemon] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + + fetch(LIST_URL) + .then((res) => { + if (!res.ok) throw new Error(`PokeAPI request failed: ${res.status}`); + return res.json(); + }) + .then((data: { results: { name: string; url: string }[] }) => { + if (cancelled) return; + const list = data.results.map(({ name, url }) => { + const id = Number(url.split("/").filter(Boolean).pop()); + return { id, name, image: ARTWORK_URL(id) }; + }); + setPokemon(list); + }) + .catch((err: Error) => { + if (!cancelled) setError(err.message); + }); + + return () => { + cancelled = true; + }; + }, []); + + if (error) return

Failed to load Pokémon: {error}

; + + return ( +
+ {pokemon.map(({ id, name, image }) => ( +
+ {name} + {capitalize(name)} +
+ ))} +
+ ); +} From c22fa92aff36f1ff8ab3b89ba6f3e02de1f01086 Mon Sep 17 00:00:00 2001 From: joelte Date: Wed, 9 Sep 2026 22:46:35 +0200 Subject: [PATCH 2/6] feat: add filterfunctionality for cardgrid Added filter fundtionality for the grid of pokemon cards. Filter now saves with html storage and pulls names from PokeAPI to autoupdate incase of new added gens or types. --- PTG/src/App.tsx | 19 ++++- PTG/src/ChoosePokemon.tsx | 144 +++++++++++++++++++++----------------- PTG/src/PokemonGrid.tsx | 78 ++++++++++++++++++++- PTG/src/generations.ts | 31 ++++++++ PTG/src/pokemonFilters.ts | 30 ++++++++ PTG/src/pokemonTypes.ts | 41 +++++++++++ 6 files changed, 274 insertions(+), 69 deletions(-) create mode 100644 PTG/src/generations.ts create mode 100644 PTG/src/pokemonFilters.ts create mode 100644 PTG/src/pokemonTypes.ts diff --git a/PTG/src/App.tsx b/PTG/src/App.tsx index 15b43c0..879d9b2 100644 --- a/PTG/src/App.tsx +++ b/PTG/src/App.tsx @@ -1,15 +1,28 @@ +import { useEffect, useState } from "react"; import Header from "./Header.tsx" import ChoosePokemon from "./ChoosePokemon.tsx"; import PokemonGrid from "./PokemonGrid.tsx"; +import { loadStoredFilters, saveStoredFilters } from "./pokemonFilters.ts"; export default function App(){ + const [selectedGen, setSelectedGen] = useState(() => loadStoredFilters().selectedGen); + const [selectedType, setSelectedType] = useState(() => loadStoredFilters().selectedType); + + useEffect(() => { + saveStoredFilters({ selectedGen, selectedType }); + }, [selectedGen, selectedType]); return(
- - + +
); -} \ No newline at end of file +} diff --git a/PTG/src/ChoosePokemon.tsx b/PTG/src/ChoosePokemon.tsx index 92e014b..8435f43 100644 --- a/PTG/src/ChoosePokemon.tsx +++ b/PTG/src/ChoosePokemon.tsx @@ -1,64 +1,77 @@ -import {useState} from "react" +import { useEffect, useState } from "react"; +import { fetchGenerations, type GenerationOption } from "./generations"; +import { fetchTypes, type TypeOption } from "./pokemonTypes"; +type Gen = { + label: string, + value: string +} +const ANY_TYPE: Gen = { label: 'Any Type', value: 'any' }; -function ChoosePokemon(){ - const [selectedGen, setSelectedGen] = useState(''); - const [selectedType, setSelectedType] = useState(''); - - type Gen = { - label: string, - value: string - } +function changeTypeArray(selectedGen: string, gens: GenerationOption[], types: TypeOption[]) { + const genIndex = gens.findIndex((gen) => gen.value === selectedGen); + const available = genIndex === -1 + ? types + : types.filter((type) => { + const typeGenIndex = gens.findIndex((gen) => gen.value === type.generation); + return typeGenIndex === -1 || typeGenIndex <= genIndex; + }); + return [ANY_TYPE, ...available]; +} + +type ChoosePokemonProps = { + selectedGen: string; + selectedType: string; + onGenChange: (value: string) => void; + onTypeChange: (value: string) => void; +} + +function ChoosePokemon({ selectedGen, selectedType, onGenChange, onTypeChange }: ChoosePokemonProps){ + const [gens, setGens] = useState([]); + const [gensError, setGensError] = useState(null); + const [types, setTypes] = useState([]); + const [typesError, setTypesError] = useState(null); + + useEffect(() => { + let cancelled = false; + + fetchGenerations() + .then((data) => { + if (!cancelled) setGens(data); + }) + .catch((err: Error) => { + if (!cancelled) setGensError(err.message); + }); - let gens: Gen[] = [ - {label: 'Gen I', value: 'gen_i'}, - {label: 'Gen II', value: 'gen_ii'}, - {label: 'Gen III', value: 'gen_iii'}, - {label: 'Gen IV', value: 'gen_iv'}, - {label: 'Gen V', value: 'gen_v'}, - {label: 'Gen VI', value: 'gen_vi'}, - {label: 'Gen VII', value: 'gen_vii'}, - {label: 'Gen VIII', value: 'gen_viii'}, - {label: 'Gen IX', value: 'gen_ix'}, - ] - - let types: Gen[] = [ - {label: 'Any', value: 'any'}, - {label: 'Fire', value: 'fire'}, - {label: 'Water', value: 'water'}, - {label: 'Grass', value: 'grass'}, - {label: 'Electric', value: 'electric'}, - {label: 'Rock', value: 'rock'}, - {label: 'Ground', value: 'ground'}, - {label: 'Flying', value: 'flying'}, - {label: 'Dragon', value: 'dragon'}, - {label: 'Bug', value: 'bug'}, - {label: 'Poison', value: 'poison'}, - {label: 'Psychic', value: 'psychic'}, - {label: 'Normal', value: 'normal'}, - {label: 'Fighting', value: 'fighting'}, - {label: 'Ghost', value: 'ghost'}, - {label: 'Ice', value: 'ice'}, - {label: 'Steel', value: 'steel'}, - {label: 'Dark', value: 'dark'}, - {label: 'Fairy', value: 'fairy'} - ] - - function changeTypeArray(){ - if (selectedGen === 'gen_i') return types.slice(0, 16); - else if (selectedGen === 'gen_ii' || selectedGen === 'gen_iii' || - selectedGen === 'gen_iv' || selectedGen === 'gen_v'){ - return types.slice(0, 18); + fetchTypes() + .then((data) => { + if (!cancelled) setTypes(data); + }) + .catch((err: Error) => { + if (!cancelled) setTypesError(err.message); + }); + + return () => { + cancelled = true; + }; + }, []); + + function handleGenChange(e: React.ChangeEvent) { + const newGen = e.target.value; + onGenChange(newGen); + const availableTypes = changeTypeArray(newGen, gens, types); + if (!availableTypes.some((t) => t.value === selectedType)) { + onTypeChange('any'); } - return types; } - function handleGenChange(e: any) { setSelectedGen(e.target.value); } + function handleTypeChange(e: React.ChangeEvent) { onTypeChange(e.target.value); } - function handleTypeChange(e: any) { setSelectedType(e.target.value); } - - let newTypes : Gen[] = changeTypeArray(); + let newTypes : Gen[] = changeTypeArray(selectedGen, gens, types); + + if (gensError) return

Failed to load generations: {gensError}

; + if (typesError) return

Failed to load types: {typesError}

; return( <> @@ -67,10 +80,10 @@ function ChoosePokemon(){ value={selectedGen} onChange={handleGenChange} > - - {gens.map((gens) => ( - + {gens.map((gen) => ( + ))} @@ -86,18 +99,23 @@ function ChoosePokemon(){ ))} - + ); } -export default function generateTeam(){ - +export default function generateTeam({ selectedGen, selectedType, onGenChange, onTypeChange }: ChoosePokemonProps){ + return( <> - + - + ); -} \ No newline at end of file +} diff --git a/PTG/src/PokemonGrid.tsx b/PTG/src/PokemonGrid.tsx index 09528a0..23fa643 100644 --- a/PTG/src/PokemonGrid.tsx +++ b/PTG/src/PokemonGrid.tsx @@ -1,10 +1,12 @@ import { useEffect, useState } from "react"; import "./PokemonGrid.css"; +import { fetchGenerationSpeciesIds } from "./generations"; -const POKEMON_COUNT = 100; +const POKEMON_COUNT = 1025; const LIST_URL = `https://pokeapi.co/api/v2/pokemon?limit=${POKEMON_COUNT}`; const ARTWORK_URL = (id: number) => `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${id}.png`; +const TYPE_URL = (type: string) => `https://pokeapi.co/api/v2/type/${type}`; type Pokemon = { id: number; @@ -16,9 +18,18 @@ function capitalize(name: string) { return name.charAt(0).toUpperCase() + name.slice(1); } -export default function PokemonGrid() { +type PokemonGridProps = { + selectedGen: string; + selectedType: string; +}; + +export default function PokemonGrid({ selectedGen, selectedType }: PokemonGridProps) { const [pokemon, setPokemon] = useState([]); const [error, setError] = useState(null); + const [typeNames, setTypeNames] = useState | null>(null); + const [typeError, setTypeError] = useState(null); + const [genIds, setGenIds] = useState | null>(null); + const [genError, setGenError] = useState(null); useEffect(() => { let cancelled = false; @@ -45,11 +56,72 @@ export default function PokemonGrid() { }; }, []); + useEffect(() => { + let cancelled = false; + + if (!selectedType || selectedType === "any") { + setTypeNames(null); + setTypeError(null); + return; + } + + fetch(TYPE_URL(selectedType)) + .then((res) => { + if (!res.ok) throw new Error(`PokeAPI request failed: ${res.status}`); + return res.json(); + }) + .then((data: { pokemon: { pokemon: { name: string } }[] }) => { + if (cancelled) return; + setTypeNames(new Set(data.pokemon.map(({ pokemon }) => pokemon.name))); + setTypeError(null); + }) + .catch((err: Error) => { + if (!cancelled) setTypeError(err.message); + }); + + return () => { + cancelled = true; + }; + }, [selectedType]); + + useEffect(() => { + let cancelled = false; + + if (!selectedGen) { + setGenIds(null); + setGenError(null); + return; + } + + fetchGenerationSpeciesIds(selectedGen) + .then((ids) => { + if (!cancelled) { + setGenIds(ids); + setGenError(null); + } + }) + .catch((err: Error) => { + if (!cancelled) setGenError(err.message); + }); + + return () => { + cancelled = true; + }; + }, [selectedGen]); + if (error) return

Failed to load Pokémon: {error}

; + if (typeError) return

Failed to load type: {typeError}

; + if (genError) return

Failed to load generation: {genError}

; + + const filtered = pokemon.filter(({ id, name }) => { + if (genIds && !genIds.has(id)) return false; + if (typeNames && !typeNames.has(name)) return false; + return true; + }); return (
- {pokemon.map(({ id, name, image }) => ( + {filtered.map(({ id, name, image }) => (
{name} {capitalize(name)} diff --git a/PTG/src/generations.ts b/PTG/src/generations.ts new file mode 100644 index 0000000..8a19384 --- /dev/null +++ b/PTG/src/generations.ts @@ -0,0 +1,31 @@ +const GENERATION_LIST_URL = "https://pokeapi.co/api/v2/generation?limit=100"; +const GENERATION_URL = (name: string) => `https://pokeapi.co/api/v2/generation/${name}`; + +export type GenerationOption = { + label: string; + value: string; +}; + +function idFromUrl(url: string): number { + return Number(url.split("/").filter(Boolean).pop()); +} + +function toLabel(name: string): string { + return `Gen ${name.replace("generation-", "").toUpperCase()}`; +} + +export async function fetchGenerations(): Promise { + const res = await fetch(GENERATION_LIST_URL); + if (!res.ok) throw new Error(`PokeAPI request failed: ${res.status}`); + const data: { results: { name: string; url: string }[] } = await res.json(); + return data.results + .sort((a, b) => idFromUrl(a.url) - idFromUrl(b.url)) + .map(({ name }) => ({ label: toLabel(name), value: name })); +} + +export async function fetchGenerationSpeciesIds(name: string): Promise> { + const res = await fetch(GENERATION_URL(name)); + if (!res.ok) throw new Error(`PokeAPI request failed: ${res.status}`); + const data: { pokemon_species: { url: string }[] } = await res.json(); + return new Set(data.pokemon_species.map((species) => idFromUrl(species.url))); +} diff --git a/PTG/src/pokemonFilters.ts b/PTG/src/pokemonFilters.ts new file mode 100644 index 0000000..654514a --- /dev/null +++ b/PTG/src/pokemonFilters.ts @@ -0,0 +1,30 @@ +const STORAGE_KEY = "pokemonFilters"; + +export type PokemonFilters = { + selectedGen: string; + selectedType: string; +}; + +const DEFAULT_FILTERS: PokemonFilters = { selectedGen: "", selectedType: "any" }; + +export function loadStoredFilters(): PokemonFilters { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return DEFAULT_FILTERS; + const parsed = JSON.parse(raw); + return { + selectedGen: typeof parsed.selectedGen === "string" ? parsed.selectedGen : DEFAULT_FILTERS.selectedGen, + selectedType: typeof parsed.selectedType === "string" ? parsed.selectedType : DEFAULT_FILTERS.selectedType, + }; + } catch { + return DEFAULT_FILTERS; + } +} + +export function saveStoredFilters(filters: PokemonFilters) { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(filters)); + } catch { + // storage unavailable - filter still works for this session + } +} diff --git a/PTG/src/pokemonTypes.ts b/PTG/src/pokemonTypes.ts new file mode 100644 index 0000000..a6f0f43 --- /dev/null +++ b/PTG/src/pokemonTypes.ts @@ -0,0 +1,41 @@ +// Type data pulled live from PokeAPI, so new types (and the generation +// that introduces them) show up automatically. +const TYPE_LIST_URL = "https://pokeapi.co/api/v2/type?limit=100"; +const TYPE_URL = (name: string) => `https://pokeapi.co/api/v2/type/${name}`; + +// "unknown" (???) and "shadow" are not real battle types you can filter by. +const NON_BATTLE_TYPE_ID = 10000; + +export type TypeOption = { + label: string; + value: string; + generation: string; +}; + +function idFromUrl(url: string): number { + return Number(url.split("/").filter(Boolean).pop()); +} + +function toLabel(name: string): string { + return name.charAt(0).toUpperCase() + name.slice(1); +} + +export async function fetchTypes(): Promise { + const listRes = await fetch(TYPE_LIST_URL); + if (!listRes.ok) throw new Error(`PokeAPI request failed: ${listRes.status}`); + const listData: { results: { name: string; url: string }[] } = await listRes.json(); + const battleTypes = listData.results.filter(({ url }) => idFromUrl(url) < NON_BATTLE_TYPE_ID); + + const details = await Promise.all( + battleTypes.map(async ({ name }) => { + const res = await fetch(TYPE_URL(name)); + if (!res.ok) throw new Error(`PokeAPI request failed: ${res.status}`); + const data: { id: number; name: string; generation: { name: string } } = await res.json(); + return data; + }) + ); + + return details + .sort((a, b) => a.id - b.id) + .map((type) => ({ label: toLabel(type.name), value: type.name, generation: type.generation.name })); +} From c45071c879ed5615a7ebe153ff3a8b09ef58b3a2 Mon Sep 17 00:00:00 2001 From: joelte Date: Sat, 12 Sep 2026 20:00:01 +0200 Subject: [PATCH 3/6] feat: Add option to filter for games Added another filter to see pokemon from a specific game. --- PTG/src/App.tsx | 15 +++-- PTG/src/ChoosePokemon.css | 5 ++ PTG/src/ChoosePokemon.tsx | 115 ++++++++++++++++++++++++++++---------- PTG/src/PokemonGrid.tsx | 59 ++++++++++++++----- PTG/src/games.ts | 70 +++++++++++++++++++++++ PTG/src/pokemonFilters.ts | 8 ++- 6 files changed, 221 insertions(+), 51 deletions(-) create mode 100644 PTG/src/ChoosePokemon.css create mode 100644 PTG/src/games.ts diff --git a/PTG/src/App.tsx b/PTG/src/App.tsx index 879d9b2..165597b 100644 --- a/PTG/src/App.tsx +++ b/PTG/src/App.tsx @@ -5,23 +5,26 @@ import PokemonGrid from "./PokemonGrid.tsx"; import { loadStoredFilters, saveStoredFilters } from "./pokemonFilters.ts"; export default function App(){ - const [selectedGen, setSelectedGen] = useState(() => loadStoredFilters().selectedGen); + const [selectedGame, setSelectedGame] = useState(() => loadStoredFilters().selectedGame); + const [selectedGeneration, setSelectedGeneration] = useState(() => loadStoredFilters().selectedGeneration); const [selectedType, setSelectedType] = useState(() => loadStoredFilters().selectedType); useEffect(() => { - saveStoredFilters({ selectedGen, selectedType }); - }, [selectedGen, selectedType]); + saveStoredFilters({ selectedGame, selectedGeneration, selectedType }); + }, [selectedGame, selectedGeneration, selectedType]); return(
- +
); diff --git a/PTG/src/ChoosePokemon.css b/PTG/src/ChoosePokemon.css new file mode 100644 index 0000000..d3edf93 --- /dev/null +++ b/PTG/src/ChoosePokemon.css @@ -0,0 +1,5 @@ +.chooseType { + field-sizing: content; + width: fit-content; + max-width: 100%; +} diff --git a/PTG/src/ChoosePokemon.tsx b/PTG/src/ChoosePokemon.tsx index 8435f43..2013602 100644 --- a/PTG/src/ChoosePokemon.tsx +++ b/PTG/src/ChoosePokemon.tsx @@ -1,47 +1,82 @@ import { useEffect, useState } from "react"; +import "./ChoosePokemon.css"; +import { fetchGames, type GameOption } from "./games"; import { fetchGenerations, type GenerationOption } from "./generations"; import { fetchTypes, type TypeOption } from "./pokemonTypes"; -type Gen = { +type TypeChoice = { label: string, value: string } -const ANY_TYPE: Gen = { label: 'Any Type', value: 'any' }; +const ANY_TYPE: TypeChoice = { label: 'Any Type', value: 'any' }; -function changeTypeArray(selectedGen: string, gens: GenerationOption[], types: TypeOption[]) { - const genIndex = gens.findIndex((gen) => gen.value === selectedGen); - const available = genIndex === -1 +function effectiveGenerationIndex( + selectedGame: string, + games: GameOption[], + selectedGeneration: string, + generations: GenerationOption[] +): number { + const indices: number[] = []; + + const game = games.find((g) => g.value === selectedGame); + if (game) { + const gameGenIndex = generations.findIndex((gen) => gen.value === game.generation); + if (gameGenIndex !== -1) indices.push(gameGenIndex); + } + + if (selectedGeneration) { + const generationIndex = generations.findIndex((gen) => gen.value === selectedGeneration); + if (generationIndex !== -1) indices.push(generationIndex); + } + + return indices.length === 0 ? -1 : Math.min(...indices); +} + +function changeTypeArray(effectiveIndex: number, types: TypeOption[], generations: GenerationOption[]) { + const available = effectiveIndex === -1 ? types : types.filter((type) => { - const typeGenIndex = gens.findIndex((gen) => gen.value === type.generation); - return typeGenIndex === -1 || typeGenIndex <= genIndex; + const typeGenIndex = generations.findIndex((gen) => gen.value === type.generation); + return typeGenIndex === -1 || typeGenIndex <= effectiveIndex; }); return [ANY_TYPE, ...available]; } type ChoosePokemonProps = { - selectedGen: string; + selectedGame: string; + selectedGeneration: string; selectedType: string; - onGenChange: (value: string) => void; + onGameChange: (value: string) => void; + onGenerationChange: (value: string) => void; onTypeChange: (value: string) => void; } -function ChoosePokemon({ selectedGen, selectedType, onGenChange, onTypeChange }: ChoosePokemonProps){ - const [gens, setGens] = useState([]); - const [gensError, setGensError] = useState(null); +function ChoosePokemon({ selectedGame, selectedGeneration, selectedType, onGameChange, onGenerationChange, onTypeChange }: ChoosePokemonProps){ + const [games, setGames] = useState([]); + const [gamesError, setGamesError] = useState(null); + const [generations, setGenerations] = useState([]); + const [generationsError, setGenerationsError] = useState(null); const [types, setTypes] = useState([]); const [typesError, setTypesError] = useState(null); useEffect(() => { let cancelled = false; + fetchGames() + .then((data) => { + if (!cancelled) setGames(data); + }) + .catch((err: Error) => { + if (!cancelled) setGamesError(err.message); + }); + fetchGenerations() .then((data) => { - if (!cancelled) setGens(data); + if (!cancelled) setGenerations(data); }) .catch((err: Error) => { - if (!cancelled) setGensError(err.message); + if (!cancelled) setGenerationsError(err.message); }); fetchTypes() @@ -57,10 +92,19 @@ function ChoosePokemon({ selectedGen, selectedType, onGenChange, onTypeChange }: }; }, []); - function handleGenChange(e: React.ChangeEvent) { - const newGen = e.target.value; - onGenChange(newGen); - const availableTypes = changeTypeArray(newGen, gens, types); + function handleGameChange(e: React.ChangeEvent) { + const newGame = e.target.value; + onGameChange(newGame); + const availableTypes = changeTypeArray(effectiveGenerationIndex(newGame, games, selectedGeneration, generations), types, generations); + if (!availableTypes.some((t) => t.value === selectedType)) { + onTypeChange('any'); + } + } + + function handleGenerationChange(e: React.ChangeEvent) { + const newGeneration = e.target.value; + onGenerationChange(newGeneration); + const availableTypes = changeTypeArray(effectiveGenerationIndex(selectedGame, games, newGeneration, generations), types, generations); if (!availableTypes.some((t) => t.value === selectedType)) { onTypeChange('any'); } @@ -68,22 +112,35 @@ function ChoosePokemon({ selectedGen, selectedType, onGenChange, onTypeChange }: function handleTypeChange(e: React.ChangeEvent) { onTypeChange(e.target.value); } - let newTypes : Gen[] = changeTypeArray(selectedGen, gens, types); + let newTypes : TypeChoice[] = changeTypeArray(effectiveGenerationIndex(selectedGame, games, selectedGeneration, generations), types, generations); - if (gensError) return

Failed to load generations: {gensError}

; + if (gamesError) return

Failed to load games: {gamesError}

; + if (generationsError) return

Failed to load generations: {generationsError}

; if (typesError) return

Failed to load types: {typesError}

; return( <> + @@ -104,14 +161,16 @@ function ChoosePokemon({ selectedGen, selectedType, onGenChange, onTypeChange }: ); } -export default function generateTeam({ selectedGen, selectedType, onGenChange, onTypeChange }: ChoosePokemonProps){ +export default function generateTeam({ selectedGame, selectedGeneration, selectedType, onGameChange, onGenerationChange, onTypeChange }: ChoosePokemonProps){ return( <> diff --git a/PTG/src/PokemonGrid.tsx b/PTG/src/PokemonGrid.tsx index 23fa643..196f3b8 100644 --- a/PTG/src/PokemonGrid.tsx +++ b/PTG/src/PokemonGrid.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import "./PokemonGrid.css"; +import { fetchGameSpeciesIds } from "./games"; import { fetchGenerationSpeciesIds } from "./generations"; const POKEMON_COUNT = 1025; @@ -19,17 +20,20 @@ function capitalize(name: string) { } type PokemonGridProps = { - selectedGen: string; + selectedGame: string; + selectedGeneration: string; selectedType: string; }; -export default function PokemonGrid({ selectedGen, selectedType }: PokemonGridProps) { +export default function PokemonGrid({ selectedGame, selectedGeneration, selectedType }: PokemonGridProps) { const [pokemon, setPokemon] = useState([]); const [error, setError] = useState(null); const [typeNames, setTypeNames] = useState | null>(null); const [typeError, setTypeError] = useState(null); - const [genIds, setGenIds] = useState | null>(null); - const [genError, setGenError] = useState(null); + const [gameIds, setGameIds] = useState | null>(null); + const [gameError, setGameError] = useState(null); + const [generationIds, setGenerationIds] = useState | null>(null); + const [generationError, setGenerationError] = useState(null); useEffect(() => { let cancelled = false; @@ -87,34 +91,61 @@ export default function PokemonGrid({ selectedGen, selectedType }: PokemonGridPr useEffect(() => { let cancelled = false; - if (!selectedGen) { - setGenIds(null); - setGenError(null); + if (!selectedGame) { + setGameIds(null); + setGameError(null); return; } - fetchGenerationSpeciesIds(selectedGen) + fetchGameSpeciesIds(selectedGame) .then((ids) => { if (!cancelled) { - setGenIds(ids); - setGenError(null); + setGameIds(ids); + setGameError(null); } }) .catch((err: Error) => { - if (!cancelled) setGenError(err.message); + if (!cancelled) setGameError(err.message); }); return () => { cancelled = true; }; - }, [selectedGen]); + }, [selectedGame]); + + useEffect(() => { + let cancelled = false; + + if (!selectedGeneration) { + setGenerationIds(null); + setGenerationError(null); + return; + } + + fetchGenerationSpeciesIds(selectedGeneration) + .then((ids) => { + if (!cancelled) { + setGenerationIds(ids); + setGenerationError(null); + } + }) + .catch((err: Error) => { + if (!cancelled) setGenerationError(err.message); + }); + + return () => { + cancelled = true; + }; + }, [selectedGeneration]); if (error) return

Failed to load Pokémon: {error}

; if (typeError) return

Failed to load type: {typeError}

; - if (genError) return

Failed to load generation: {genError}

; + if (gameError) return

Failed to load game: {gameError}

; + if (generationError) return

Failed to load generation: {generationError}

; const filtered = pokemon.filter(({ id, name }) => { - if (genIds && !genIds.has(id)) return false; + if (gameIds && !gameIds.has(id)) return false; + if (generationIds && !generationIds.has(id)) return false; if (typeNames && !typeNames.has(name)) return false; return true; }); diff --git a/PTG/src/games.ts b/PTG/src/games.ts new file mode 100644 index 0000000..96df9ec --- /dev/null +++ b/PTG/src/games.ts @@ -0,0 +1,70 @@ +const VERSION_GROUP_LIST_URL = "https://pokeapi.co/api/v2/version-group?limit=100"; +const VERSION_GROUP_URL = (name: string) => `https://pokeapi.co/api/v2/version-group/${name}`; + +export type GameOption = { + label: string; + value: string; + generation: string; +}; + +function idFromUrl(url: string): number { + return Number(url.split("/").filter(Boolean).pop()); +} + +function toLabel(name: string): string { + return name + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +type VersionGroup = { + name: string; + url: string; +}; + +type VersionGroupDetail = { + generation: { name: string }; + pokedexes: { name: string; url: string }[]; +}; + +export async function fetchGames(): Promise { + const res = await fetch(VERSION_GROUP_LIST_URL); + if (!res.ok) throw new Error(`PokeAPI request failed: ${res.status}`); + const data: { results: VersionGroup[] } = await res.json(); + + const details = await Promise.all( + data.results.map(async (versionGroup) => { + const detailRes = await fetch(versionGroup.url); + if (!detailRes.ok) throw new Error(`PokeAPI request failed: ${detailRes.status}`); + const detail: VersionGroupDetail = await detailRes.json(); + return { versionGroup, detail }; + }) + ); + + return details + .filter(({ detail }) => detail.pokedexes.length > 0) + .sort((a, b) => idFromUrl(a.versionGroup.url) - idFromUrl(b.versionGroup.url)) + .map(({ versionGroup, detail }) => ({ + label: toLabel(versionGroup.name), + value: versionGroup.name, + generation: detail.generation.name, + })); +} + +export async function fetchGameSpeciesIds(name: string): Promise> { + const res = await fetch(VERSION_GROUP_URL(name)); + if (!res.ok) throw new Error(`PokeAPI request failed: ${res.status}`); + const data: VersionGroupDetail = await res.json(); + + const idLists = await Promise.all( + data.pokedexes.map(async (pokedex) => { + const pokedexRes = await fetch(pokedex.url); + if (!pokedexRes.ok) throw new Error(`PokeAPI request failed: ${pokedexRes.status}`); + const pokedexData: { pokemon_entries: { pokemon_species: { url: string } }[] } = await pokedexRes.json(); + return pokedexData.pokemon_entries.map(({ pokemon_species }) => idFromUrl(pokemon_species.url)); + }) + ); + + return new Set(idLists.flat()); +} diff --git a/PTG/src/pokemonFilters.ts b/PTG/src/pokemonFilters.ts index 654514a..8fd555d 100644 --- a/PTG/src/pokemonFilters.ts +++ b/PTG/src/pokemonFilters.ts @@ -1,11 +1,12 @@ const STORAGE_KEY = "pokemonFilters"; export type PokemonFilters = { - selectedGen: string; + selectedGame: string; + selectedGeneration: string; selectedType: string; }; -const DEFAULT_FILTERS: PokemonFilters = { selectedGen: "", selectedType: "any" }; +const DEFAULT_FILTERS: PokemonFilters = { selectedGame: "", selectedGeneration: "", selectedType: "any" }; export function loadStoredFilters(): PokemonFilters { try { @@ -13,7 +14,8 @@ export function loadStoredFilters(): PokemonFilters { if (!raw) return DEFAULT_FILTERS; const parsed = JSON.parse(raw); return { - selectedGen: typeof parsed.selectedGen === "string" ? parsed.selectedGen : DEFAULT_FILTERS.selectedGen, + selectedGame: typeof parsed.selectedGame === "string" ? parsed.selectedGame : DEFAULT_FILTERS.selectedGame, + selectedGeneration: typeof parsed.selectedGeneration === "string" ? parsed.selectedGeneration : DEFAULT_FILTERS.selectedGeneration, selectedType: typeof parsed.selectedType === "string" ? parsed.selectedType : DEFAULT_FILTERS.selectedType, }; } catch { From c804724e21937446a86bc479c429dc7109a58335 Mon Sep 17 00:00:00 2001 From: joelte Date: Sun, 13 Sep 2026 00:23:01 +0200 Subject: [PATCH 4/6] docs: Add comments to explain none obvious code Added some comments to explain some of the code snippets that are not self explanitory --- PTG/src/ChoosePokemon.tsx | 5 +++++ PTG/src/PokemonGrid.tsx | 1 + PTG/src/games.ts | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/PTG/src/ChoosePokemon.tsx b/PTG/src/ChoosePokemon.tsx index 2013602..1ec5248 100644 --- a/PTG/src/ChoosePokemon.tsx +++ b/PTG/src/ChoosePokemon.tsx @@ -11,6 +11,7 @@ type TypeChoice = { const ANY_TYPE: TypeChoice = { label: 'Any Type', value: 'any' }; +// Earliest generation implied by the selected game and/or generation filter (-1 if neither is set). function effectiveGenerationIndex( selectedGame: string, games: GameOption[], @@ -33,6 +34,7 @@ function effectiveGenerationIndex( return indices.length === 0 ? -1 : Math.min(...indices); } +// Types introduced after the effective generation aren't offered yet. function changeTypeArray(effectiveIndex: number, types: TypeOption[], generations: GenerationOption[]) { const available = effectiveIndex === -1 ? types @@ -95,6 +97,7 @@ function ChoosePokemon({ selectedGame, selectedGeneration, selectedType, onGameC function handleGameChange(e: React.ChangeEvent) { const newGame = e.target.value; onGameChange(newGame); + // Drop the type filter if it's no longer valid for the new game const availableTypes = changeTypeArray(effectiveGenerationIndex(newGame, games, selectedGeneration, generations), types, generations); if (!availableTypes.some((t) => t.value === selectedType)) { onTypeChange('any'); @@ -104,6 +107,7 @@ function ChoosePokemon({ selectedGame, selectedGeneration, selectedType, onGameC function handleGenerationChange(e: React.ChangeEvent) { const newGeneration = e.target.value; onGenerationChange(newGeneration); + // Drop the type filter if it's no longer valid for the new generation const availableTypes = changeTypeArray(effectiveGenerationIndex(selectedGame, games, newGeneration, generations), types, generations); if (!availableTypes.some((t) => t.value === selectedType)) { onTypeChange('any'); @@ -161,6 +165,7 @@ function ChoosePokemon({ selectedGame, selectedGeneration, selectedType, onGameC ); } +// Filter controls plus the (not yet wired up) team generation button. export default function generateTeam({ selectedGame, selectedGeneration, selectedType, onGameChange, onGenerationChange, onTypeChange }: ChoosePokemonProps){ return( diff --git a/PTG/src/PokemonGrid.tsx b/PTG/src/PokemonGrid.tsx index 196f3b8..6cf46a0 100644 --- a/PTG/src/PokemonGrid.tsx +++ b/PTG/src/PokemonGrid.tsx @@ -143,6 +143,7 @@ export default function PokemonGrid({ selectedGame, selectedGeneration, selected if (gameError) return

Failed to load game: {gameError}

; if (generationError) return

Failed to load generation: {generationError}

; + // Each filter is resolved to a matching-id/name set independently above; a Pokémon must satisfy all of them. const filtered = pokemon.filter(({ id, name }) => { if (gameIds && !gameIds.has(id)) return false; if (generationIds && !generationIds.has(id)) return false; diff --git a/PTG/src/games.ts b/PTG/src/games.ts index 96df9ec..789b62e 100644 --- a/PTG/src/games.ts +++ b/PTG/src/games.ts @@ -43,7 +43,7 @@ export async function fetchGames(): Promise { ); return details - .filter(({ detail }) => detail.pokedexes.length > 0) + .filter(({ detail }) => detail.pokedexes.length > 0) // exclude spin-off version groups with no pokedex .sort((a, b) => idFromUrl(a.versionGroup.url) - idFromUrl(b.versionGroup.url)) .map(({ versionGroup, detail }) => ({ label: toLabel(versionGroup.name), From 0e5fbcf4114b0ff811631f59af7fd3b3e0e58b08 Mon Sep 17 00:00:00 2001 From: joelte Date: Sun, 13 Sep 2026 00:33:00 +0200 Subject: [PATCH 5/6] feat: Add icons for typings Added icons for the types for every pokemon by pulling their id and fetchign an icon from a github repo for pokemon --- PTG/src/PokemonGrid.css | 22 +++++++++++++++++- PTG/src/PokemonGrid.tsx | 43 +++++++++++++++++++++++++++++----- PTG/src/pokemonTypes.ts | 51 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 7 deletions(-) diff --git a/PTG/src/PokemonGrid.css b/PTG/src/PokemonGrid.css index db5d7b2..8d25505 100644 --- a/PTG/src/PokemonGrid.css +++ b/PTG/src/PokemonGrid.css @@ -15,10 +15,30 @@ text-align: center; } -.pokemonCard img { +.pokemonCardImage { + position: relative; width: 100%; max-width: 120px; +} + +.pokemonCardImage > img { + width: 100%; height: auto; + display: block; +} + +.pokemonCardTypes { + position: absolute; + top: 2px; + left: 2px; + display: flex; + gap: 2px; +} + +.pokemonCardTypes img { + width: 20px; + height: 20px; + filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.6)); } .pokemonGridError { diff --git a/PTG/src/PokemonGrid.tsx b/PTG/src/PokemonGrid.tsx index 6cf46a0..ba0455b 100644 --- a/PTG/src/PokemonGrid.tsx +++ b/PTG/src/PokemonGrid.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import "./PokemonGrid.css"; import { fetchGameSpeciesIds } from "./games"; import { fetchGenerationSpeciesIds } from "./generations"; +import { fetchPokemonTypeMap, TYPE_ICON_URL, type PokemonType } from "./pokemonTypes"; const POKEMON_COUNT = 1025; const LIST_URL = `https://pokeapi.co/api/v2/pokemon?limit=${POKEMON_COUNT}`; @@ -34,6 +35,7 @@ export default function PokemonGrid({ selectedGame, selectedGeneration, selected const [gameError, setGameError] = useState(null); const [generationIds, setGenerationIds] = useState | null>(null); const [generationError, setGenerationError] = useState(null); + const [typeMap, setTypeMap] = useState | null>(null); useEffect(() => { let cancelled = false; @@ -60,6 +62,23 @@ export default function PokemonGrid({ selectedGame, selectedGeneration, selected }; }, []); + useEffect(() => { + let cancelled = false; + + // Decorative type icons; a failure here shouldn't block the rest of the grid. + fetchPokemonTypeMap() + .then((map) => { + if (!cancelled) setTypeMap(map); + }) + .catch((err: Error) => { + console.error("Failed to load Pokémon types:", err.message); + }); + + return () => { + cancelled = true; + }; + }, []); + useEffect(() => { let cancelled = false; @@ -153,12 +172,24 @@ export default function PokemonGrid({ selectedGame, selectedGeneration, selected return (
- {filtered.map(({ id, name, image }) => ( -
- {name} - {capitalize(name)} -
- ))} + {filtered.map(({ id, name, image }) => { + const types = typeMap?.get(name); + return ( +
+
+ {name} + {types && types.length > 0 && ( +
+ {types.map((type) => ( + {type.name} + ))} +
+ )} +
+ {capitalize(name)} +
+ ); + })}
); } diff --git a/PTG/src/pokemonTypes.ts b/PTG/src/pokemonTypes.ts index a6f0f43..a862291 100644 --- a/PTG/src/pokemonTypes.ts +++ b/PTG/src/pokemonTypes.ts @@ -6,12 +6,21 @@ const TYPE_URL = (name: string) => `https://pokeapi.co/api/v2/type/${name}`; // "unknown" (???) and "shadow" are not real battle types you can filter by. const NON_BATTLE_TYPE_ID = 10000; +// Official Scarlet/Violet type icon, indexed by the same numeric type id PokeAPI uses. +export const TYPE_ICON_URL = (id: number) => + `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-ix/scarlet-violet/small/${id}.png`; + export type TypeOption = { label: string; value: string; generation: string; }; +export type PokemonType = { + id: number; + name: string; +}; + function idFromUrl(url: string): number { return Number(url.split("/").filter(Boolean).pop()); } @@ -39,3 +48,45 @@ export async function fetchTypes(): Promise { .sort((a, b) => a.id - b.id) .map((type) => ({ label: toLabel(type.name), value: type.name, generation: type.generation.name })); } + +type TypeDetail = { + id: number; + name: string; + pokemon: { slot: number; pokemon: { name: string } }[]; +}; + +// Maps each Pokemon name to its types, ordered by slot (slot 1 = primary type). +export async function fetchPokemonTypeMap(): Promise> { + const listRes = await fetch(TYPE_LIST_URL); + if (!listRes.ok) throw new Error(`PokeAPI request failed: ${listRes.status}`); + const listData: { results: { name: string; url: string }[] } = await listRes.json(); + const battleTypes = listData.results.filter(({ url }) => idFromUrl(url) < NON_BATTLE_TYPE_ID); + + const details = await Promise.all( + battleTypes.map(async ({ name }) => { + const res = await fetch(TYPE_URL(name)); + if (!res.ok) throw new Error(`PokeAPI request failed: ${res.status}`); + const data: TypeDetail = await res.json(); + return data; + }) + ); + + const bySlot = new Map(); + for (const type of details) { + for (const { slot, pokemon } of type.pokemon) { + const entries = bySlot.get(pokemon.name) ?? []; + entries.push({ slot, type: { id: type.id, name: type.name } }); + bySlot.set(pokemon.name, entries); + } + } + + const typeMap = new Map(); + for (const [name, entries] of bySlot) { + typeMap.set( + name, + entries.sort((a, b) => a.slot - b.slot).map((entry) => entry.type) + ); + } + + return typeMap; +} From d86c739a1da6b5dddff0e29fa68e1b425258ad2a Mon Sep 17 00:00:00 2001 From: joelte Date: Mon, 14 Sep 2026 12:53:44 +0200 Subject: [PATCH 6/6] feat: Remove filter for generations Removed the filter for generation, now pokemon can only be sorted by type and game. --- PTG/src/App.tsx | 9 ++-- PTG/src/ChoosePokemon.tsx | 88 ++------------------------------------- PTG/src/PokemonGrid.tsx | 33 +-------------- PTG/src/games.ts | 5 +-- PTG/src/generations.ts | 31 -------------- PTG/src/pokemonFilters.ts | 4 +- PTG/src/pokemonTypes.ts | 18 ++------ 7 files changed, 14 insertions(+), 174 deletions(-) delete mode 100644 PTG/src/generations.ts diff --git a/PTG/src/App.tsx b/PTG/src/App.tsx index 165597b..4a616aa 100644 --- a/PTG/src/App.tsx +++ b/PTG/src/App.tsx @@ -6,25 +6,22 @@ import { loadStoredFilters, saveStoredFilters } from "./pokemonFilters.ts"; export default function App(){ const [selectedGame, setSelectedGame] = useState(() => loadStoredFilters().selectedGame); - const [selectedGeneration, setSelectedGeneration] = useState(() => loadStoredFilters().selectedGeneration); const [selectedType, setSelectedType] = useState(() => loadStoredFilters().selectedType); useEffect(() => { - saveStoredFilters({ selectedGame, selectedGeneration, selectedType }); - }, [selectedGame, selectedGeneration, selectedType]); + saveStoredFilters({ selectedGame, selectedType }); + }, [selectedGame, selectedType]); return(
- +
); diff --git a/PTG/src/ChoosePokemon.tsx b/PTG/src/ChoosePokemon.tsx index 1ec5248..d2e7c78 100644 --- a/PTG/src/ChoosePokemon.tsx +++ b/PTG/src/ChoosePokemon.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from "react"; import "./ChoosePokemon.css"; import { fetchGames, type GameOption } from "./games"; -import { fetchGenerations, type GenerationOption } from "./generations"; import { fetchTypes, type TypeOption } from "./pokemonTypes"; type TypeChoice = { @@ -11,54 +10,16 @@ type TypeChoice = { const ANY_TYPE: TypeChoice = { label: 'Any Type', value: 'any' }; -// Earliest generation implied by the selected game and/or generation filter (-1 if neither is set). -function effectiveGenerationIndex( - selectedGame: string, - games: GameOption[], - selectedGeneration: string, - generations: GenerationOption[] -): number { - const indices: number[] = []; - - const game = games.find((g) => g.value === selectedGame); - if (game) { - const gameGenIndex = generations.findIndex((gen) => gen.value === game.generation); - if (gameGenIndex !== -1) indices.push(gameGenIndex); - } - - if (selectedGeneration) { - const generationIndex = generations.findIndex((gen) => gen.value === selectedGeneration); - if (generationIndex !== -1) indices.push(generationIndex); - } - - return indices.length === 0 ? -1 : Math.min(...indices); -} - -// Types introduced after the effective generation aren't offered yet. -function changeTypeArray(effectiveIndex: number, types: TypeOption[], generations: GenerationOption[]) { - const available = effectiveIndex === -1 - ? types - : types.filter((type) => { - const typeGenIndex = generations.findIndex((gen) => gen.value === type.generation); - return typeGenIndex === -1 || typeGenIndex <= effectiveIndex; - }); - return [ANY_TYPE, ...available]; -} - type ChoosePokemonProps = { selectedGame: string; - selectedGeneration: string; selectedType: string; onGameChange: (value: string) => void; - onGenerationChange: (value: string) => void; onTypeChange: (value: string) => void; } -function ChoosePokemon({ selectedGame, selectedGeneration, selectedType, onGameChange, onGenerationChange, onTypeChange }: ChoosePokemonProps){ +function ChoosePokemon({ selectedGame, selectedType, onGameChange, onTypeChange }: ChoosePokemonProps){ const [games, setGames] = useState([]); const [gamesError, setGamesError] = useState(null); - const [generations, setGenerations] = useState([]); - const [generationsError, setGenerationsError] = useState(null); const [types, setTypes] = useState([]); const [typesError, setTypesError] = useState(null); @@ -73,14 +34,6 @@ function ChoosePokemon({ selectedGame, selectedGeneration, selectedType, onGameC if (!cancelled) setGamesError(err.message); }); - fetchGenerations() - .then((data) => { - if (!cancelled) setGenerations(data); - }) - .catch((err: Error) => { - if (!cancelled) setGenerationsError(err.message); - }); - fetchTypes() .then((data) => { if (!cancelled) setTypes(data); @@ -94,32 +47,13 @@ function ChoosePokemon({ selectedGame, selectedGeneration, selectedType, onGameC }; }, []); - function handleGameChange(e: React.ChangeEvent) { - const newGame = e.target.value; - onGameChange(newGame); - // Drop the type filter if it's no longer valid for the new game - const availableTypes = changeTypeArray(effectiveGenerationIndex(newGame, games, selectedGeneration, generations), types, generations); - if (!availableTypes.some((t) => t.value === selectedType)) { - onTypeChange('any'); - } - } - - function handleGenerationChange(e: React.ChangeEvent) { - const newGeneration = e.target.value; - onGenerationChange(newGeneration); - // Drop the type filter if it's no longer valid for the new generation - const availableTypes = changeTypeArray(effectiveGenerationIndex(selectedGame, games, newGeneration, generations), types, generations); - if (!availableTypes.some((t) => t.value === selectedType)) { - onTypeChange('any'); - } - } + function handleGameChange(e: React.ChangeEvent) { onGameChange(e.target.value); } function handleTypeChange(e: React.ChangeEvent) { onTypeChange(e.target.value); } - let newTypes : TypeChoice[] = changeTypeArray(effectiveGenerationIndex(selectedGame, games, selectedGeneration, generations), types, generations); + const newTypes: TypeChoice[] = [ANY_TYPE, ...types]; if (gamesError) return

Failed to load games: {gamesError}

; - if (generationsError) return

Failed to load generations: {generationsError}

; if (typesError) return

Failed to load types: {typesError}

; return( @@ -136,18 +70,6 @@ function ChoosePokemon({ selectedGame, selectedGeneration, selectedType, onGameC ))} -