import { useState, useCallback } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import toast from 'react-hot-toast'; import { combatApi, characterApi } from '../api/endpoints'; import type { Monster, CombatResult, MultiCombatResult } from '../api/types'; import { Swords, Clock, Zap, Heart, Lock } from 'lucide-react'; import { COMBAT_COST, REST_COST, ATTACK_TYPES, ZONE_INFO } from '../constants'; import { MonsterCard } from '../components/MonsterCard'; import { CombatLogView, MultiCombatView, HistoryEntry } from '../components/CombatViews'; export function CombatPage() { const qc = useQueryClient(); const [selectedMonster, setSelectedMonster] = useState(null); const [attackType, setAttackType] = useState('melee'); const [lastResult, setLastResult] = useState(null); const [lastMultiResult, setLastMultiResult] = useState(null); const [cooldown, setCooldown] = useState(false); const { data: char } = useQuery({ queryKey: ['character'], queryFn: characterApi.me }); const endurance = char?.enduranceCurrent ?? 0; const playerLevel = char?.level ?? 1; const canFight = endurance >= COMBAT_COST; const needsHeal = char ? char.hpCurrent < char.hpMax : false; const canHeal = needsHeal && endurance >= REST_COST; const healMut = useMutation({ mutationFn: () => characterApi.rest(), onSuccess: () => qc.invalidateQueries({ queryKey: ['character'] }), }); const { data: monsters, isLoading } = useQuery({ queryKey: ['monsters'], queryFn: combatApi.monsters, }); const { data: zones } = useQuery({ queryKey: ['zones'], queryFn: combatApi.zones, }); const { data: history } = useQuery({ queryKey: ['combatHistory'], queryFn: combatApi.history, }); const startCooldown = useCallback(() => { setCooldown(true); setTimeout(() => setCooldown(false), 1500); }, []); const fight = useMutation({ mutationFn: (count: number = 1) => combatApi.start(selectedMonster!.id, attackType, count), onSuccess: (result) => { if (result.mode === 'multi') { setLastMultiResult(result as MultiCombatResult); setLastResult(null); } else { setLastResult(result as CombatResult); setLastMultiResult(null); } qc.invalidateQueries({ queryKey: ['character'] }); qc.invalidateQueries({ queryKey: ['combatHistory'] }); qc.invalidateQueries({ queryKey: ['questsActive'] }); qc.invalidateQueries({ queryKey: ['materialsInventory'] }); startCooldown(); }, onError: (err: Error) => { toast.error(err.message); startCooldown(); }, }); if (isLoading) return
Chargement des monstres…
; // Group monsters by zone const monstersByZone = new Map(); for (const m of (monsters ?? [])) { const zone = (m as any).zone ?? 'marais'; const list = monstersByZone.get(zone) ?? []; list.push(m); monstersByZone.set(zone, list); } const ZONE_LABELS = ZONE_INFO; // Locked zones (zones not in monsters response = locked) const lockedZones = (zones ?? []).filter((z: any) => !z.unlocked); return (

⚔️ Combat

{/* Choix monstre par zone */}
{Array.from(monstersByZone.entries()).map(([zone, zoneMonsters]) => { const info = ZONE_LABELS[zone] ?? { name: zone, emoji: '📍' }; return (

{info.emoji} {info.name}

{zoneMonsters .sort((a, b) => a.minLevel - b.minLevel) .map(m => ( setSelectedMonster(m)} playerLevel={playerLevel} /> ))}
); })} {/* Zones verrouillées */} {lockedZones.map((z: any) => (
{z.emoji} {z.name} — Complétez l'arc précédent pour débloquer
))}
{/* Panneau droite */}
{/* Type d'attaque */}

Type d'attaque

{ATTACK_TYPES.map(a => (
setAttackType(a.id)} style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }} > {a.emoji}
{a.label}
{a.stat}
))}
{/* Soins rapide */} {needsHeal && ( )} {/* Coût endurance */}
Coût : {COMBAT_COST} endurance — Disponible : {endurance} {canFight && ({Math.floor(endurance / COMBAT_COST)} combats)}
{/* Boutons combattre */}
{[1, 5, 10].map(n => ( ))}
{fight.isError && (

{(fight.error as Error).message}

)} {/* Historique récent */} {history && history.length > 0 && (

Historique récent

{history.slice(0, 10).map(h => )}
)}
{/* Résultat du dernier combat */} {lastMultiResult && } {lastResult && }
); }