From bf896a797f82956938427440c48cc8d97606507d Mon Sep 17 00:00:00 2001 From: Tetardtek Date: Tue, 24 Mar 2026 18:58:15 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20vente=20items=20+=20stats=20combat=20av?= =?UTF-8?q?ec=20=C3=A9quipement=20+=20forge=20visible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inventaire: bouton Vendre sur items non équipés (40% du prix d'achat). Stats forge visibles: "+5 ATK (3+2)" montre base + bonus forge. Dashboard combat: attaque/défense calculés avec arme+armure+forge équipées. 10 side quests Égouts seedées (level 5-7). --- frontend/src/pages/DashboardPage.tsx | 59 ++++++++++++++++++---------- frontend/src/pages/InventoryPage.tsx | 37 ++++++++++++++--- 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 8de8b1f..1eab148 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { characterApi } from '../api/endpoints'; +import { characterApi, itemApi } from '../api/endpoints'; import { Bar } from '../components/Bar'; import { Zap, Heart, Star, Coins, Sword, Shield, BedDouble } from 'lucide-react'; @@ -134,6 +134,42 @@ function StatDistributor({ char }: { char: any }) { ); } +function CombatStatsPanel({ char }: { char: any }) { + const { data: inventory } = useQuery({ queryKey: ['inventory'], queryFn: itemApi.inventory }); + + const weapon = inventory?.find((ci: any) => ci.equipped && ci.item.type === 'weapon'); + const armor = inventory?.find((ci: any) => ci.equipped && ci.item.type === 'armor'); + + const weaponATK = weapon ? weapon.item.attackBonus + weapon.forgeLevel * 2 : 0; + const armorDEF = armor ? armor.item.defenseBonus + armor.forgeLevel * 2 : 0; + const baseDmg = 3 + weaponATK + Math.floor(char.force * 1.5); + + return ( +
+

Combat actuel

+
+
+ + Attaque : + {baseDmg} + {weapon && ({weapon.item.name} {weapon.forgeLevel > 0 ? `+${weapon.forgeLevel}` : ''})} +
+
+ + Défense : + {armorDEF} + {armor && ({armor.item.name} {armor.forgeLevel > 0 ? `+${armor.forgeLevel}` : ''})} +
+
+ + Critique : + {(5 + char.chance * 0.2).toFixed(1)}% +
+
+
+ ); +} + export function DashboardPage() { const qc = useQueryClient(); const { data: char, isLoading, isError } = useQuery({ @@ -266,26 +302,7 @@ export function DashboardPage() { )} {/* Équipement résumé */} -
-

Combat actuel

-
-
- - Attaque : - {Math.floor(char.force * 1.5)} -
-
- - Critique : - {(5 + char.chance * 0.2).toFixed(1)}% -
-
- - Esquive : - {(5 + char.chance * 0.1).toFixed(1)}% -
-
-
+ ); diff --git a/frontend/src/pages/InventoryPage.tsx b/frontend/src/pages/InventoryPage.tsx index 6361576..2bb12e6 100644 --- a/frontend/src/pages/InventoryPage.tsx +++ b/frontend/src/pages/InventoryPage.tsx @@ -1,17 +1,26 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { itemApi, materialApi } from '../api/endpoints'; +import { api } from '../api/client'; import type { CharacterItem } from '../api/types'; -import { Package, Sword, Shield } from 'lucide-react'; +import { Package, Sword, Shield, Coins } from 'lucide-react'; const RARITY_LABEL: Record = { common: 'Commun', rare: 'Rare', epic: 'Épique', legendary: 'Légendaire', }; -function ItemCard({ ci, onEquip, onUnequip }: { ci: CharacterItem; onEquip: () => void; onUnequip: () => void }) { +function ItemCard({ ci, onEquip, onUnequip, onSell, selling }: { + ci: CharacterItem; onEquip: () => void; onUnequip: () => void; onSell: () => void; selling: boolean; +}) { const { item } = ci; + const forgeBonusATK = item.type === 'weapon' ? ci.forgeLevel * 2 : 0; + const forgeBonusDEF = item.type === 'armor' ? ci.forgeLevel * 2 : 0; + const totalATK = item.attackBonus + forgeBonusATK; + const totalDEF = item.defenseBonus + forgeBonusDEF; + const sellPrice = Math.floor((item as any).buyPrice * 0.4) || 0; + const bonuses = [ - item.attackBonus && `+${item.attackBonus} ATK`, - item.defenseBonus && `+${item.defenseBonus} DEF`, + totalATK > 0 && `+${totalATK} ATK${forgeBonusATK > 0 ? ` (${item.attackBonus}+${forgeBonusATK})` : ''}`, + totalDEF > 0 && `+${totalDEF} DEF${forgeBonusDEF > 0 ? ` (${item.defenseBonus}+${forgeBonusDEF})` : ''}`, item.forceBonus && `+${item.forceBonus} FOR`, item.agiliteBonus && `+${item.agiliteBonus} AGI`, item.intelligenceBonus && `+${item.intelligenceBonus} INT`, @@ -37,11 +46,17 @@ function ItemCard({ ci, onEquip, onUnequip }: { ci: CharacterItem; onEquip: () = {bonuses &&
{bonuses}
} -
+
{!ci.equipped ? : } + {!ci.equipped && sellPrice > 0 && ( + + )}
); @@ -70,6 +85,14 @@ export function InventoryPage() { onSuccess: () => qc.invalidateQueries({ queryKey: ['inventory'] }), }); + const sellMut = useMutation({ + mutationFn: (charItemId: string) => api.post(`/shop/sell/${charItemId}`), + onSuccess: () => { + qc.invalidateQueries({ queryKey: ['inventory'] }); + qc.invalidateQueries({ queryKey: ['character'] }); + }, + }); + if (loadInv || loadMat) return
Chargement…
; const weapons = inventory?.filter(ci => ci.item.type === 'weapon') ?? []; @@ -99,6 +122,8 @@ export function InventoryPage() { key={ci.id} ci={ci} onEquip={() => equipMut.mutate(ci.id)} onUnequip={() => unequipMut.mutate('weapon')} + onSell={() => sellMut.mutate(ci.id)} + selling={sellMut.isPending} /> ))} @@ -117,6 +142,8 @@ export function InventoryPage() { key={ci.id} ci={ci} onEquip={() => equipMut.mutate(ci.id)} onUnequip={() => unequipMut.mutate('armor')} + onSell={() => sellMut.mutate(ci.id)} + selling={sellMut.isPending} /> ))}