From b9a49686c150207fe9636df39cd28d312a7c5bc9 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:44:15 +0000 Subject: [PATCH] refactor(*): Implement CSV-based seeding, dynamic box placement, and enforce forms from pokedex configuration --- data/pokemon/README.md | 155 ++ data/pokemon/games.csv | 6 + data/pokemon/gen1-kanto.csv | 175 ++ scripts/csv-to-migration.js | 180 ++ scripts/sql-to-csv.js | 87 + .../pokedex/PokedexEntryCatchRecord.svelte | 11 - .../components/pokedex/PokedexSidebar.svelte | 7 - .../pokedex/PokedexViewBoxes.svelte | 1 - src/lib/models/PokedexEntry.ts | 6 - .../repositories/CombinedDataRepository.ts | 65 +- .../repositories/PokedexEntryRepository.ts | 37 +- src/lib/utils/boxPlacement.ts | 41 + src/routes/pokedex/[id]/+page.svelte | 26 +- .../20250126000002_create_pokemon_schema.sql | 14 +- .../20250126000004_seed_full_pokemon_data.sql | 1462 ----------------- ...0260103181600_add_regional_dex_numbers.sql | 25 - ...0104000002_create_regional_dex_numbers.sql | 21 + .../migrations/20260104000003_seed_kanto.sql | 360 ++++ 18 files changed, 1054 insertions(+), 1625 deletions(-) create mode 100644 data/pokemon/README.md create mode 100644 data/pokemon/games.csv create mode 100644 data/pokemon/gen1-kanto.csv create mode 100644 scripts/csv-to-migration.js create mode 100644 scripts/sql-to-csv.js create mode 100644 src/lib/utils/boxPlacement.ts delete mode 100644 supabase/migrations/20250126000004_seed_full_pokemon_data.sql delete mode 100644 supabase/migrations/20260103181600_add_regional_dex_numbers.sql create mode 100644 supabase/migrations/20260104000002_create_regional_dex_numbers.sql create mode 100644 supabase/migrations/20260104000003_seed_kanto.sql diff --git a/data/pokemon/README.md b/data/pokemon/README.md new file mode 100644 index 0000000..68ee236 --- /dev/null +++ b/data/pokemon/README.md @@ -0,0 +1,155 @@ +# Pokémon Data (CSV Format) + +This directory contains Pokémon data in CSV format for easy editing and maintenance. + +## File Structure + +``` +/data/pokemon/ +├── gen1-kanto.csv # Gen 1 Pokémon (Kanto region, #1-151) +├── gen2-johto.csv # Gen 2 Pokémon (Johto region, #152-251) - Future +├── gen3-hoenn.csv # Gen 3 Pokémon (Hoenn region, #252-386) - Future +├── games.csv # All Pokémon games definitions +└── README.md # This file +``` + +## CSV Format + +### gen{N}-{region}.csv + +One CSV file per generation/region containing all Pokémon from that generation. + +**Columns:** +- `pokedexNumber` - National Pokédex number (e.g., 1, 25, 151) +- `name` - Pokémon name (e.g., Bulbasaur, Pikachu, Mew) +- `form` - Form name (empty for base form, "Female" for gender differences, "Alolan" for regional forms, etc.) +- `games` - Pipe-separated list of games where this Pokémon can be caught (e.g., "Red|Blue|Yellow") +- `regionalNumber` - Regional Pokédex number for base forms (empty for gender/alternative forms) + +**Example (gen1-kanto.csv):** +```csv +pokedexNumber,name,form,games,regionalNumber +1,Bulbasaur,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,1 +3,Venusaur,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,3 +3,Venusaur,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +25,Pikachu,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,25 +151,Mew,,Red|Blue|Yellow,151 +``` + +### games.csv + +Defines all Pokémon games. + +**Columns:** +- `id` - Unique game identifier (lowercase, hyphenated, e.g., "red", "lg-pikachu") +- `displayName` - Display name for the game (e.g., "Red", "LG: Pikachu") +- `region` - Region/generation the game belongs to (e.g., "Kanto", "Johto") +- `generation` - Generation number (1-9) + +**Example:** +```csv +id,displayName,region,generation +red,Red,Kanto,1 +blue,Blue,Kanto,1 +lg-pikachu,LG: Pikachu,Kanto,7 +``` + +## Editing with Excel/Google Sheets + +CSV files are designed to be edited in spreadsheet applications: + +1. Open the CSV file in Excel or Google Sheets +2. Use formulas, sorting, and filtering for bulk operations +3. Save as CSV when done + +**Example workflows:** + +### Adding a new game to existing Pokémon +Find & Replace in the `games` column: +- Find: `Red|Blue` +- Replace: `Red|Blue|NewGame` + +### Adding a new Pokémon +Add a new row with all required fields: +```csv +152,Chikorita,,Gold|Silver|Crystal,1 +``` + +### Adding a regional form +Add a new row with the form name: +```csv +19,Rattata,Alolan,Sun|Moon|US|UM, +``` + +## Generating SQL Migrations + +After editing CSV files, generate SQL migrations using npm scripts: + +```bash +# Generate migration for Kanto +npm run seed:kanto + +# Generate migration for Johto (when ready) +npm run seed:johto + +# Custom region +npm run seed:generate +``` + +Generated migrations will be created in `/supabase/migrations/` with timestamp prefixes. + +## Testing Changes + +After generating a migration: + +```bash +# Reset local database to test +npm run supabase:reset + +# Verify the changes loaded correctly +``` + +## Important Notes + +1. **Regional Numbers**: Only base forms (no `form` value) should have `regionalNumber` values. Forms like "Female" or "Alolan" should leave this column empty. + +2. **Game Names**: Must exactly match the `displayName` in `games.csv`. Use pipe `|` as separator, no spaces around pipes. + +3. **National Dex Numbers**: Must be accurate. Gender forms and regional variants share the same `pokedexNumber` as their base form. + +4. **CSV Encoding**: Save files with UTF-8 encoding to support special characters. + +5. **Quotes**: Only use quotes around values containing commas. Excel/Google Sheets handles this automatically. + +## Common Operations + +### Add a new generation (e.g., Gen 2 - Johto) + +1. Create `gen2-johto.csv` with the format above +2. Add Johto games to `games.csv` +3. Run `npm run seed:johto` +4. Test with `npm run supabase:reset` + +### Add a new game to existing generation + +1. Open `games.csv`, add new game row +2. Open the relevant `gen{N}-{region}.csv` file +3. Use Find & Replace to add the new game to the `games` column +4. Regenerate the migration: `npm run seed:{region}` +5. Test with `npm run supabase:reset` + +### Add DLC/expansion Pokémon + +1. Add new Pokémon rows to the appropriate generation CSV +2. Update the `games` column with the DLC games +3. Regenerate the migration +4. Test + +## Database Schema + +Generated migrations insert data into two tables: + +- **`pokedex_entries`**: Pokémon species data (name, form, games, etc.) +- **`regional_dex_numbers`**: Regional Pokédex numbers (separate table for scalability) + +This design allows adding new regions without database schema changes. diff --git a/data/pokemon/games.csv b/data/pokemon/games.csv new file mode 100644 index 0000000..b242991 --- /dev/null +++ b/data/pokemon/games.csv @@ -0,0 +1,6 @@ +id,displayName,region,generation +red,Red,Kanto,1 +blue,Blue,Kanto,1 +yellow,Yellow,Kanto,1 +lg--pikachu,LG: Pikachu,Kanto,7 +lg--eevee,LG: Eevee,Kanto,7 \ No newline at end of file diff --git a/data/pokemon/gen1-kanto.csv b/data/pokemon/gen1-kanto.csv new file mode 100644 index 0000000..f811bd6 --- /dev/null +++ b/data/pokemon/gen1-kanto.csv @@ -0,0 +1,175 @@ +pokedexNumber,name,form,games,regionalNumber +1,Bulbasaur,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,1 +2,Ivysaur,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,2 +3,Venusaur,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,3 +3,Venusaur,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +4,Charmander,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,4 +5,Charmeleon,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,5 +6,Charizard,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,6 +7,Squirtle,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,7 +8,Wartortle,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,8 +9,Blastoise,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,9 +10,Caterpie,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,10 +11,Metapod,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,11 +12,Butterfree,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,12 +12,Butterfree,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +13,Weedle,,Red|Blue|LG: Pikachu|LG: Eevee,13 +14,Kakuna,,Red|Blue|LG: Pikachu|LG: Eevee,14 +15,Beedrill,,Red|Blue|LG: Pikachu|LG: Eevee,15 +16,Pidgey,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,16 +17,Pidgeotto,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,17 +18,Pidgeot,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,18 +19,Rattata,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,19 +19,Rattata,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +20,Raticate,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,20 +20,Raticate,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +21,Spearow,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,21 +22,Fearow,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,22 +23,Ekans,,Red|Blue|LG: Eevee,23 +24,Arbok,,Red|Blue|LG: Eevee,24 +25,Pikachu,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,25 +25,Pikachu,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +26,Raichu,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,26 +26,Raichu,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +27,Sandshrew,,Blue|Yellow|LG: Pikachu,27 +28,Sandslash,,Blue|Yellow|LG: Pikachu,28 +29,Nidoran♀,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,29 +30,Nidorina,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,30 +31,Nidoqueen,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,31 +32,Nidoran♂,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,32 +33,Nidorino,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,33 +34,Nidoking,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,34 +35,Clefairy,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,35 +36,Clefable,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,36 +37,Vulpix,,Blue|Yellow|LG: Eevee,37 +38,Ninetales,,Blue|Yellow|LG: Eevee,38 +39,Jigglypuff,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,39 +40,Wigglytuff,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,40 +41,Zubat,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,41 +41,Zubat,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +42,Golbat,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,42 +42,Golbat,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +43,Oddish,,Red|Yellow|LG: Pikachu,43 +44,Gloom,,Red|Yellow|LG: Pikachu,44 +44,Gloom,Female,Red|Yellow|LG: Pikachu, +45,Vileplume,,Red|Yellow|LG: Pikachu,45 +45,Vileplume,Female,Red|Yellow|LG: Pikachu, +46,Paras,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,46 +47,Parasect,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,47 +48,Venonat,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,48 +49,Venomoth,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,49 +50,Diglett,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,50 +51,Dugtrio,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,51 +52,Meowth,,Blue|LG: Eevee,52 +53,Persian,,Blue|LG: Eevee,53 +54,Psyduck,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,54 +55,Golduck,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,55 +56,Mankey,,Red|Yellow|LG: Pikachu,56 +57,Primeape,,Red|Yellow|LG: Pikachu,57 +58,Growlithe,,Red|Yellow|LG: Pikachu,58 +59,Arcanine,,Red|Yellow|LG: Pikachu|LG: Eevee,59 +60,Poliwag,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,60 +61,Poliwhirl,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,61 +62,Poliwrath,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,62 +63,Abra,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,63 +64,Kadabra,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,64 +64,Kadabra,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +65,Alakazam,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,65 +65,Alakazam,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +66,Machop,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,66 +67,Machoke,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,67 +68,Machamp,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,68 +69,Bellsprout,,Blue|Yellow|LG: Eevee,69 +70,Weepinbell,,Blue|Yellow|LG: Eevee,70 +71,Victreebel,,Blue|Yellow|LG: Eevee,71 +72,Tentacool,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,72 +73,Tentacruel,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,73 +74,Geodude,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,74 +75,Graveler,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,75 +76,Golem,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,76 +77,Ponyta,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,77 +78,Rapidash,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,78 +79,Slowpoke,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,79 +80,Slowbro,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,80 +81,Magnemite,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,81 +82,Magneton,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,82 +83,Farfetch’d,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,83 +84,Doduo,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,84 +84,Doduo,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +85,Dodrio,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,85 +85,Dodrio,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +86,Seel,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,86 +87,Dewgong,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,87 +88,Grimer,,Red|Blue|Yellow|LG: Pikachu,88 +89,Muk,,Red|Blue|Yellow|LG: Pikachu,89 +90,Shellder,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,90 +91,Cloyster,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,91 +92,Gastly,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,92 +93,Haunter,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,93 +94,Gengar,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,94 +95,Onix,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,95 +96,Drowzee,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,96 +97,Hypno,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,97 +97,Hypno,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +98,Krabby,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,98 +99,Kingler,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,99 +100,Voltorb,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,100 +101,Electrode,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,101 +102,Exeggcute,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,102 +103,Exeggutor,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,103 +104,Cubone,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,104 +105,Marowak,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,105 +106,Hitmonlee,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,106 +107,Hitmonchan,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,107 +108,Lickitung,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,108 +109,Koffing,,Red|Blue|LG: Eevee,109 +110,Weezing,,Red|Blue|LG: Eevee,110 +111,Rhyhorn,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,111 +111,Rhyhorn,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +112,Rhydon,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,112 +112,Rhydon,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +113,Chansey,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,113 +114,Tangela,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,114 +115,Kangaskhan,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,115 +116,Horsea,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,116 +117,Seadra,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,117 +118,Goldeen,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,118 +118,Goldeen,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +119,Seaking,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,119 +119,Seaking,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +120,Staryu,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,120 +121,Starmie,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,121 +122,Mr. Mime,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,122 +123,Scyther,,Red|Yellow|LG: Pikachu,123 +123,Scyther,Female,Red|Yellow|LG: Pikachu, +124,Jynx,,Red|Blue|LG: Pikachu|LG: Eevee,124 +125,Electabuzz,,Red|LG: Pikachu|LG: Eevee,125 +126,Magmar,,Blue|LG: Pikachu|LG: Eevee,126 +127,Pinsir,,Blue|Yellow|LG: Eevee,127 +128,Tauros,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,128 +129,Magikarp,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,129 +129,Magikarp,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +130,Gyarados,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,130 +130,Gyarados,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +131,Lapras,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,131 +132,Ditto,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,132 +133,Eevee,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,133 +133,Eevee,Female,Red|Blue|Yellow|LG: Pikachu|LG: Eevee, +134,Vaporeon,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,134 +135,Jolteon,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,135 +136,Flareon,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,136 +137,Porygon,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,137 +138,Omanyte,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,138 +139,Omastar,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,139 +140,Kabuto,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,140 +141,Kabutops,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,141 +142,Aerodactyl,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,142 +143,Snorlax,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,143 +144,Articuno,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,144 +145,Zapdos,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,145 +146,Moltres,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,146 +147,Dratini,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,147 +148,Dragonair,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,148 +149,Dragonite,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,149 +150,Mewtwo,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,150 +151,Mew,,Red|Blue|Yellow|LG: Pikachu|LG: Eevee,151 \ No newline at end of file diff --git a/scripts/csv-to-migration.js b/scripts/csv-to-migration.js new file mode 100644 index 0000000..a21ad65 --- /dev/null +++ b/scripts/csv-to-migration.js @@ -0,0 +1,180 @@ +#!/usr/bin/env node + +/** + * Generates SQL migration from CSV files + * Usage: node csv-to-migration.js + * Example: node csv-to-migration.js Kanto + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Parse CSV file into array of objects + */ +function parseCSV(filePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n').filter(line => line.trim()); + const headers = lines[0].split(','); + + return lines.slice(1).map(line => { + const values = parseCSVLine(line); + const obj = {}; + headers.forEach((header, i) => { + obj[header] = values[i] || null; + }); + return obj; + }); +} + +/** + * Parse a single CSV line, handling quoted values + */ +function parseCSVLine(line) { + const values = []; + let current = ''; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const char = line[i]; + + if (char === '"') { + inQuotes = !inQuotes; + } else if (char === ',' && !inQuotes) { + values.push(current); + current = ''; + } else { + current += char; + } + } + values.push(current); + + return values; +} + +/** + * Generate migration SQL from CSV data + */ +function generateMigration(region) { + console.log(`\nGenerating migration for ${region}...`); + + // Determine generation number from region + const regionToGen = { + 'Kanto': 1, 'Johto': 2, 'Hoenn': 3, 'Sinnoh': 4, + 'Unova': 5, 'Kalos': 6, 'Alola': 7, 'Galar': 8, + 'Hisui': 8, 'Paldea': 9 + }; + const gen = regionToGen[region] || 1; + + // 1. Load CSV files + const pokemonPath = path.join(__dirname, '..', 'data', 'pokemon', `gen${gen}-${region.toLowerCase()}.csv`); + const gamesPath = path.join(__dirname, '..', 'data', 'pokemon', 'games.csv'); + + if (!fs.existsSync(pokemonPath)) { + console.error(`Error: Pokemon CSV not found at ${pokemonPath}`); + process.exit(1); + } + + const pokemon = parseCSV(pokemonPath); + const games = parseCSV(gamesPath); + + // 2. Filter for this region + const regionGames = games.filter(g => g.region === region); + + if (regionGames.length === 0) { + console.error(`Error: No games found for region ${region} in games.csv`); + process.exit(1); + } + + // 3. Generate SQL + let sql = `-- Seed ${region} region Pokémon (Gen ${gen})\n`; + sql += `-- Auto-generated from CSV files\n\n`; + + // Region-game mappings + sql += `-- Insert ${region} region-game mappings\n`; + sql += `INSERT INTO region_game_mappings (region, game) VALUES\n`; + sql += regionGames.map(g => ` ('${region}', '${g.displayName}')`).join(',\n'); + sql += `\nON CONFLICT (region, game) DO NOTHING;\n\n`; + + // Pokemon entries (without regional dex number) + sql += `-- Insert ${region} Pokémon entries\n`; + sql += `INSERT INTO pokedex_entries (\n`; + sql += ` "pokedexNumber",\n`; + sql += ` pokemon,\n`; + sql += ` form,\n`; + sql += ` "canGigantamax",\n`; + sql += ` "regionToCatchIn",\n`; + sql += ` "gamesToCatchIn"\n`; + sql += `) VALUES\n`; + + const rows = pokemon.map(p => { + const form = p.form ? `'${p.form}'` : 'NULL'; + const gamesList = p.games.split('|'); + const gamesArray = `ARRAY[${gamesList.map(g => `'${g}'`).join(', ')}]`; + + return `(${p.pokedexNumber}, '${p.name}', ${form}, false, '${region}', ${gamesArray})`; + }); + + sql += rows.join(',\n'); + sql += '\nON CONFLICT ON CONSTRAINT unique_pokemon_form DO NOTHING;\n\n'; + + // Regional dex numbers (separate table) + sql += `-- Insert ${region} regional dex numbers\n`; + sql += `INSERT INTO regional_dex_numbers (\n`; + sql += ` pokedex_entry_id,\n`; + sql += ` region,\n`; + sql += ` dex_number\n`; + sql += `) VALUES\n`; + + const dexRows = pokemon + .filter(p => p.regionalNumber) // Only entries with regional dex numbers + .map(p => { + const formCondition = p.form + ? `form = '${p.form}'` + : `form IS NULL`; + + return ` ((SELECT id FROM pokedex_entries WHERE "pokedexNumber" = ${p.pokedexNumber} AND ${formCondition}), '${region}', ${p.regionalNumber})`; + }); + + if (dexRows.length > 0) { + sql += dexRows.join(',\n'); + sql += ';\n\n'; + } else { + sql += ' -- No regional dex numbers for this region\n\n'; + } + + // Add metadata + sql += `-- Add metadata\n`; + sql += `INSERT INTO metadata (key, value) VALUES\n`; + sql += ` ('${region.toLowerCase()}_seeded', 'true'),\n`; + sql += ` ('${region.toLowerCase()}_seed_date', NOW()::TEXT),\n`; + sql += ` ('${region.toLowerCase()}_pokemon_count', '${pokemon.filter(p => !p.form).length}');\n`; + + // 4. Write file + const timestamp = new Date().toISOString().replace(/[-:.]/g, '').slice(0, 14); + const filename = `${timestamp}_seed_${region.toLowerCase()}.sql`; + const outputPath = path.join(__dirname, '..', 'supabase', 'migrations', filename); + + fs.writeFileSync(outputPath, sql); + + console.log(`✓ Generated ${filename}`); + console.log(` - ${pokemon.length} Pokemon entries`); + console.log(` - ${dexRows.length} regional dex numbers`); + console.log(` - ${regionGames.length} games\n`); + + return filename; +} + +// Run +const region = process.argv[2]; +if (!region) { + console.error('Usage: node csv-to-migration.js '); + console.error('Example: node csv-to-migration.js Kanto'); + process.exit(1); +} + +generateMigration(region); diff --git a/scripts/sql-to-csv.js b/scripts/sql-to-csv.js new file mode 100644 index 0000000..b28d67f --- /dev/null +++ b/scripts/sql-to-csv.js @@ -0,0 +1,87 @@ +#!/usr/bin/env node + +/** + * Converts Kanto seed SQL migration to CSV format + * Parses 20260104000001_seed_kanto.sql and generates: + * - data/pokemon/gen1-kanto.csv + * - data/pokemon/games.csv + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Read the SQL migration file +const sqlPath = path.join(__dirname, '..', 'supabase', 'migrations', '20260104000001_seed_kanto.sql'); +const sqlContent = fs.readFileSync(sqlPath, 'utf-8'); + +// Extract games from region_game_mappings section +const gameMatches = [...sqlContent.matchAll(/\('Kanto', '([^']+)'\)/g)]; +const games = gameMatches.map(m => m[1]); + +console.log(`Found ${games.length} Kanto games:`, games); + +// Extract Pokemon entries from INSERT statement +// Match pattern: (number, 'name', form, boolean, 'region', ARRAY[games], ...) +const valuePattern = /\((\d+),\s*'([^']+)',\s*(NULL|'[^']+'),\s*(true|false),\s*'[^']+',\s*ARRAY\[([^\]]+)\][^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,\s*(NULL|\d+)\)/g; + +const pokemon = []; +let match; +while ((match = valuePattern.exec(sqlContent)) !== null) { + const [_, pokedexNumber, name, formRaw, canGigantamax, gamesRaw, regionalNumberRaw] = match; + + // Parse form (NULL or 'Female') + const form = formRaw === 'NULL' ? '' : formRaw.replace(/'/g, ''); + + // Parse games array - extract game names from ARRAY['Red', 'Blue', ...] + const gamesList = gamesRaw.match(/'([^']+)'/g).map(g => g.replace(/'/g, '')); + const gamesStr = gamesList.join('|'); + + // Parse regional number + const regionalNumber = regionalNumberRaw === 'NULL' ? '' : regionalNumberRaw; + + pokemon.push({ + pokedexNumber, + name, + form, + games: gamesStr, + regionalNumber + }); +} + +console.log(`Extracted ${pokemon.length} Pokemon entries`); + +// Generate gen1-kanto.csv +const kantoCSV = [ + 'pokedexNumber,name,form,games,regionalNumber', + ...pokemon.map(p => { + const form = p.form.includes(',') ? `"${p.form}"` : p.form; + const games = p.games.includes(',') ? `"${p.games}"` : p.games; + return `${p.pokedexNumber},${p.name},${form},${games},${p.regionalNumber}`; + }) +].join('\n'); + +const kantoPath = path.join(__dirname, '..', 'data', 'pokemon', 'gen1-kanto.csv'); +fs.writeFileSync(kantoPath, kantoCSV); +console.log(`✓ Created ${kantoPath}`); + +// Generate games.csv +const gamesCSV = [ + 'id,displayName,region,generation', + ...games.map((game, idx) => { + const id = game.toLowerCase().replace(/[^a-z0-9]/g, '-'); + const generation = game.startsWith('LG:') ? 7 : 1; + return `${id},${game},Kanto,${generation}`; + }) +].join('\n'); + +const gamesPath = path.join(__dirname, '..', 'data', 'pokemon', 'games.csv'); +fs.writeFileSync(gamesPath, gamesCSV); +console.log(`✓ Created ${gamesPath}`); + +console.log('\n✅ Conversion complete!'); +console.log(` gen1-kanto.csv: ${pokemon.length} entries`); +console.log(` games.csv: ${games.length} games`); diff --git a/src/lib/components/pokedex/PokedexEntryCatchRecord.svelte b/src/lib/components/pokedex/PokedexEntryCatchRecord.svelte index 659126c..fc914b6 100644 --- a/src/lib/components/pokedex/PokedexEntryCatchRecord.svelte +++ b/src/lib/components/pokedex/PokedexEntryCatchRecord.svelte @@ -60,17 +60,6 @@ {/if}
- {#if showForms} -

Box: {pokedexEntry.boxPlacementForms.box}

-

Row: {pokedexEntry.boxPlacementForms.row}

-

Column: {pokedexEntry.boxPlacementForms.column}

-

Can Gigantamax: {pokedexEntry.canGigantamax ? 'Yes' : 'No'}

- {:else} -

Box: {pokedexEntry.boxPlacement.box}

-

Row: {pokedexEntry.boxPlacement.row}

-

Column: {pokedexEntry.boxPlacement.column}

- {/if} - {#if pokedexEntry.evolutionInformation}

How to evolve: {pokedexEntry.evolutionInformation}

{:else} diff --git a/src/lib/components/pokedex/PokedexSidebar.svelte b/src/lib/components/pokedex/PokedexSidebar.svelte index 28790b5..183aea1 100644 --- a/src/lib/components/pokedex/PokedexSidebar.svelte +++ b/src/lib/components/pokedex/PokedexSidebar.svelte @@ -7,12 +7,10 @@ export let currentPage = 1; export let itemsPerPage = 20; export let totalPages = 0; - export let showForms = true; export let showOrigins = true; export let showShiny = false; export let catchRegion = ''; export let catchGame = ''; - export let toggleForms = () => {}; export let toggleOrigins = () => {}; export let toggleShiny = () => {}; export let getData = () => {}; @@ -57,11 +55,6 @@

Filters

    -
  • - -