diff --git a/package.json b/package.json index 74f5653..2572868 100644 --- a/package.json +++ b/package.json @@ -30,5 +30,9 @@ "typescript": "~5.8.3", "typescript-eslint": "^8.39.0", "vite": "^7.1.0" + }, + "volta": { + "node": "22.12.0", + "npm": "10.5.0" } } diff --git a/src/components/NPCPartyBuilder.tsx b/src/components/NPCPartyBuilder.tsx index 63fbe05..680ea5f 100644 --- a/src/components/NPCPartyBuilder.tsx +++ b/src/components/NPCPartyBuilder.tsx @@ -1,8 +1,9 @@ import { useState } from 'react'; -import { Plus, Trash2, Shuffle, Zap, Search } from 'lucide-react'; +import { Plus, Trash2, Shuffle, Zap, Search, Grid, List } from 'lucide-react'; import type { NPCConfiguration, NPCPartyProvider, SimplePartyProvider, PoolPartyProvider, PoolEntry } from '../types/npc'; import { pokemonApi } from '../services/pokemonApi'; import { PokemonFormSelector } from './PokemonFormSelector'; +import { PokemonCard } from './PokemonCard'; interface NPCPartyBuilderProps { config: NPCConfiguration; @@ -14,6 +15,8 @@ export function NPCPartyBuilder({ config, onChange }: NPCPartyBuilderProps) { const [showPokemonFormSelector, setShowPokemonFormSelector] = useState(false); const [selectedPokemonIndex, setSelectedPokemonIndex] = useState(-1); const [isGenerating, setIsGenerating] = useState(false); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); + const [currentPokemonString, setCurrentPokemonString] = useState(''); // Ajout de l'état pour le Pokémon actuel const handlePartyChange = (party: NPCPartyProvider) => { onChange({ ...config, party }); @@ -77,6 +80,14 @@ export function NPCPartyBuilder({ config, onChange }: NPCPartyBuilderProps) { const openPokemonFormSelector = (index: number) => { setSelectedPokemonIndex(index); + + // Si on modifie un Pokémon existant, récupérer sa chaîne actuelle + if (partyType === 'simple') { + const party = config.party as SimplePartyProvider; + const existingPokemon = party.pokemon[index]; + setCurrentPokemonString(existingPokemon || ''); + } + setShowPokemonFormSelector(true); }; @@ -126,16 +137,47 @@ export function NPCPartyBuilder({ config, onChange }: NPCPartyBuilderProps) { }; return ( -
-
-

Pokemon List

-
+
+ {/* Header with controls */} +
+
+

Pokemon Team

+ + {party.pokemon.length} Pokemon + +
+ + {/* Display mode and add controls */} +
+
+ + +
+
{/* Team Generation Tools */} -
-
Team Generation
+
+
+ + Team Generation +
@@ -175,76 +220,79 @@ export function NPCPartyBuilder({ config, onChange }: NPCPartyBuilderProps) { } }} disabled={isGenerating} - className="text-xs border border-gray-300 rounded px-2 py-1 disabled:opacity-50" + className="text-sm border border-gray-300 rounded-lg px-3 py-2 disabled:opacity-50 bg-white hover:bg-gray-50 transition-colors" > - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
- {party.pokemon.map((pokemon, index) => ( -
- updatePokemon(index, e.target.value)} - className="flex-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm" - placeholder="pikachu level=50 moves=thunderbolt,quick-attack" - /> - - + {/* Pokemon List/Grid */} + {party.pokemon.length === 0 ? ( +
+
+ +
+

No Pokémon in the team

+

Start by adding a Pokémon

- ))} + ) : ( +
+ {party.pokemon.map((pokemon, index) => ( + + ))} +
+ )} -
+ {/* Static configuration */} +
-

If true, party won't change between battles

+

+ If true, party won't change between battles +

); @@ -468,7 +516,12 @@ export function NPCPartyBuilder({ config, onChange }: NPCPartyBuilderProps) { setShowPokemonFormSelector(false)} + onClose={() => { + setShowPokemonFormSelector(false); + setSelectedPokemonIndex(-1); + setCurrentPokemonString(''); + }} + initialPokemonString={currentPokemonString} /> ); diff --git a/src/components/PokemonCard.tsx b/src/components/PokemonCard.tsx new file mode 100644 index 0000000..bcd767f --- /dev/null +++ b/src/components/PokemonCard.tsx @@ -0,0 +1,173 @@ +import { useState, useEffect } from 'react'; +import { Search, Trash2, ImageOff } from 'lucide-react'; +import { pokemonApi } from '../services/pokemonApi'; + +interface PokemonCardProps { + pokemonString: string; + index: number; + onUpdate: (index: number, value: string) => void; + onRemove: (index: number) => void; + onOpenSelector: (index: number) => void; + isCompact?: boolean; +} + +export function PokemonCard({ + pokemonString, + index, + onUpdate, + onRemove, + onOpenSelector, + isCompact = false +}: PokemonCardProps) { + const [pokemonInfo, setPokemonInfo] = useState<{ + name: string; + imageUrl: string | null; + displayName: string; + } | null>(null); + const [loading, setLoading] = useState(false); + const [imageError, setImageError] = useState(false); + + useEffect(() => { + const loadPokemonInfo = async () => { + if (!pokemonString.trim()) { + setPokemonInfo(null); + return; + } + + setLoading(true); + setImageError(false); + try { + const info = await pokemonApi.getPokemonInfo(pokemonString); + setPokemonInfo(info); + } catch (error) { + console.error('Failed to load Pokemon info:', error); + setPokemonInfo(null); + } finally { + setLoading(false); + } + }; + + loadPokemonInfo(); + }, [pokemonString]); + + const handleImageError = () => { + setImageError(true); + }; + + if (isCompact) { + return ( +
+ {/* Image du Pokémon */} +
+ {loading ? ( +
+ ) : pokemonInfo?.imageUrl && !imageError ? ( + {pokemonInfo.displayName} + ) : ( + + )} +
+ + {/* Informations et contrôles */} +
+ onUpdate(index, e.target.value)} + className="w-full text-sm border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500" + placeholder="pikachu level=50 moves=thunderbolt,quick-attack" + /> + {pokemonInfo && ( +

{pokemonInfo.displayName}

+ )} +
+ + {/* Boutons d'action */} +
+ + +
+
+ ); + } + + return ( +
+
+ {/* Image du Pokémon */} +
+ {loading ? ( +
+ ) : pokemonInfo?.imageUrl && !imageError ? ( + {pokemonInfo.displayName} + ) : ( + + )} +
+ + {/* Nom du Pokémon */} + {pokemonInfo && ( +
+

{pokemonInfo.displayName}

+

#{index + 1}

+
+ )} + + {/* Champ de saisie */} +
+