From 9eff6d541ee093968eee2517077ee57b942dd824 Mon Sep 17 00:00:00 2001 From: Tetardtek Date: Tue, 24 Mar 2026 23:50:55 +0100 Subject: [PATCH] =?UTF-8?q?refacto:=20d=C3=A9coupage=20composants=20?= =?UTF-8?q?=E2=80=94=205=20extractions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MonsterCard, CombatViews (Log+Multi+History), CreateCharacter - RarityBadge + RarityDot partagés (Guide, Drawer, pages) - CombatPage 341→215 lignes (−37%) - DashboardPage 368→307 lignes (−17%) - 9 composants dans components/ --- frontend/src/components/CombatViews.tsx | 101 +++++++++++++++ frontend/src/components/CreateCharacter.tsx | 70 ++++++++++ frontend/src/components/GuideDrawer.tsx | 10 +- frontend/src/components/MonsterCard.tsx | 30 +++++ frontend/src/components/RarityBadge.tsx | 22 ++++ frontend/src/pages/CombatPage.tsx | 134 +------------------- frontend/src/pages/DashboardPage.tsx | 65 +--------- frontend/src/pages/GuidePage.tsx | 14 +- 8 files changed, 232 insertions(+), 214 deletions(-) create mode 100644 frontend/src/components/CombatViews.tsx create mode 100644 frontend/src/components/CreateCharacter.tsx create mode 100644 frontend/src/components/MonsterCard.tsx create mode 100644 frontend/src/components/RarityBadge.tsx diff --git a/frontend/src/components/CombatViews.tsx b/frontend/src/components/CombatViews.tsx new file mode 100644 index 0000000..85f399b --- /dev/null +++ b/frontend/src/components/CombatViews.tsx @@ -0,0 +1,101 @@ +import type { CombatResult, MultiCombatResult, CombatLog } from '../api/types'; +import { Trophy, Skull } from 'lucide-react'; + +export function CombatLogView({ result }: { result: CombatResult }) { + const won = result.winner === 'player'; + return ( +
+
+ {won + ?
+ + Victoire ! +{result.rewards.xp} XP +{result.rewards.gold} or +
+ :
+ + Défaite… Retour à l'auberge +
+ } + {result.rewards.loot && ( +
+ 🎁 Loot : {result.rewards.loot.name} ×{result.rewards.loot.quantity} +
+ )} + {result.rewards.levelUp && ( +
+ 🎉 LEVEL UP ! Niveau {result.rewards.newLevel} — +{result.rewards.statPointsGained} points de stats +
+ )} +
+ +

+ Log — {result.rounds.length} tour{result.rounds.length > 1 ? 's' : ''} +

+
+ {result.rounds.flatMap(r => + r.log.map((line, i) => { + const cls = line.includes('CRITIQUE') ? 'log-crit' + : line.includes('esquive') ? 'log-crit' + : line.includes('HP') ? 'log-system' + : i === 0 ? 'log-player' + : 'log-monster'; + return
[T{r.round}] {line}
; + }) + )} + {won + ?
══ Victoire ══
+ :
══ Défaite ══
+ } +
+
+ ); +} + +export function MultiCombatView({ result }: { result: MultiCombatResult }) { + const t = result.totals; + return ( +
+
+
0 ? '#e84040' : '#3ddc84' }}> + {t.losses > 0 ? : } + {result.count} combat{result.count > 1 ? 's' : ''} — {t.wins}V / {t.losses}D +
+
+ +{t.xp} XP +{t.gold} Or + {t.goldLost > 0 && −{t.goldLost} Or} +
+ {t.levelsGained > 0 && ( +
+ 🎉 {t.levelsGained} level up{t.levelsGained > 1 ? 's' : ''} ! +
+ )} + {t.loot.length > 0 && ( +
+ 🎁 Loot : {t.loot.reduce((sum, l) => sum + l.quantity, 0)} matériaux +
+ )} + {t.losses > 0 && ( +
+ Série interrompue par une défaite +
+ )} +
+
+ ); +} + +export function HistoryEntry({ h }: { h: CombatLog }) { + return ( +
+ + {h.winner === 'player' ? '✓' : '✗'} {h.monster.name} + + + {h.winner === 'player' + ? `+${h.xpEarned}xp +${h.goldEarned}or${h.lootQuantity > 0 ? ` 🎁×${h.lootQuantity}` : ''}` + : `${h.totalRounds} tours` + } + +
+ ); +} diff --git a/frontend/src/components/CreateCharacter.tsx b/frontend/src/components/CreateCharacter.tsx new file mode 100644 index 0000000..0b06cc9 --- /dev/null +++ b/frontend/src/components/CreateCharacter.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { characterApi } from '../api/endpoints'; +import { STAT_LABELS } from '../constants'; + +const STATS = ['force', 'agilite', 'intelligence', 'chance', 'vitalite'] as const; + +export function CreateCharacter() { + const qc = useQueryClient(); + const [name, setName] = useState(''); + const [pts, setPts] = useState>({ force:1, agilite:1, intelligence:1, chance:1, vitalite:1 }); + const used = Object.values(pts).reduce((a, b) => a + b, 0) - 5; + const remaining = 5 - used; + + const mut = useMutation({ + mutationFn: () => characterApi.create(name, pts), + onSuccess: () => qc.invalidateQueries({ queryKey: ['character'] }), + }); + + const adjust = (stat: string, delta: number) => { + const next = (pts[stat] ?? 1) + delta; + if (next < 1 || next > 10) return; + if (delta > 0 && remaining <= 0) return; + setPts(p => ({ ...p, [stat]: next })); + }; + + return ( +
+
+

Créer ton personnage

+

+ {remaining > 0 ? `${remaining} point${remaining > 1 ? 's' : ''} à répartir` : 'Tous les points répartis'} +

+ + setName(e.target.value)} + style={{ marginBottom: '1rem' }} + maxLength={30} + /> + +
+ {STATS.map(s => ( +
+ {STAT_LABELS[s]} +
+ + {pts[s]} + +
+
+ ))} +
+ + + + {mut.isError &&

{(mut.error as Error).message}

} +
+
+ ); +} diff --git a/frontend/src/components/GuideDrawer.tsx b/frontend/src/components/GuideDrawer.tsx index 344bee7..6f4bf86 100644 --- a/frontend/src/components/GuideDrawer.tsx +++ b/frontend/src/components/GuideDrawer.tsx @@ -1,14 +1,8 @@ import { useState, useEffect, useRef } from 'react'; import { Search, X } from 'lucide-react'; import { useGuideData } from '../hooks/useGuideData'; - -const RARITY_COLORS: Record = { - common: '#9ca3af', rare: '#5ba4f5', epic: '#a78bfa', legendary: '#f4c94e', -}; - -function RarityDot({ rarity }: { rarity: string }) { - return ; -} +import { RARITY_COLORS } from '../constants'; +import { RarityDot } from './RarityBadge'; export function GuideDrawer({ open, onClose }: { open: boolean; onClose: () => void }) { const [search, setSearch] = useState(''); diff --git a/frontend/src/components/MonsterCard.tsx b/frontend/src/components/MonsterCard.tsx new file mode 100644 index 0000000..b695d2d --- /dev/null +++ b/frontend/src/components/MonsterCard.tsx @@ -0,0 +1,30 @@ +import type { Monster } from '../api/types'; + +export function MonsterCard({ m, selected, onSelect, playerLevel }: { + m: Monster; selected: boolean; onSelect: () => void; playerLevel: number; +}) { + const tooHard = m.minLevel > playerLevel + 2; + + return ( +
+
+ {m.name} + + Niv. {m.minLevel}–{m.maxLevel} + +
+
+ ❤️ {m.hp} + ⚔️ {m.attack} + 🛡️ {m.defense} + ⭐ {m.xpReward} XP + 💰 {m.goldMin}–{m.goldMax} +
+ {tooHard &&
Niveau trop élevé
} +
+ ); +} diff --git a/frontend/src/components/RarityBadge.tsx b/frontend/src/components/RarityBadge.tsx new file mode 100644 index 0000000..cc6fe3f --- /dev/null +++ b/frontend/src/components/RarityBadge.tsx @@ -0,0 +1,22 @@ +import { RARITY_COLORS, RARITY_LABELS } from '../constants'; + +export function RarityBadge({ rarity }: { rarity: string }) { + return ( + + {RARITY_LABELS[rarity] ?? rarity} + + ); +} + +export function RarityDot({ rarity }: { rarity: string }) { + return ( + + ); +} diff --git a/frontend/src/pages/CombatPage.tsx b/frontend/src/pages/CombatPage.tsx index a16ba8e..c4c475b 100644 --- a/frontend/src/pages/CombatPage.tsx +++ b/frontend/src/pages/CombatPage.tsx @@ -2,137 +2,11 @@ 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, CombatLog } from '../api/types'; -import { Swords, Trophy, Skull, Clock, Zap, Heart, Lock } from 'lucide-react'; - +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'; - -function MonsterCard({ m, selected, onSelect, playerLevel }: { m: Monster; selected: boolean; onSelect: () => void; playerLevel: number }) { - const tooHard = m.minLevel > playerLevel + 2; - const tooEasy = m.maxLevel < playerLevel - 3; - - return ( -
-
- {m.name} - - Niv. {m.minLevel}–{m.maxLevel} - -
-
- ❤️ {m.hp} - ⚔️ {m.attack} - 🛡️ {m.defense} - ⭐ {m.xpReward} XP - 💰 {m.goldMin}–{m.goldMax} -
- {tooHard &&
Niveau trop élevé
} -
- ); -} - -function CombatLogView({ result }: { result: CombatResult }) { - const won = result.winner === 'player'; - return ( -
-
- {won - ?
- - Victoire ! +{result.rewards.xp} XP +{result.rewards.gold} or -
- :
- - Défaite… Retour à l'auberge -
- } - {result.rewards.loot && ( -
- 🎁 Loot : {result.rewards.loot.name} ×{result.rewards.loot.quantity} -
- )} - {result.rewards.levelUp && ( -
- 🎉 LEVEL UP ! Niveau {result.rewards.newLevel} — +{result.rewards.statPointsGained} points de stats -
- )} -
- -

- Log — {result.rounds.length} tour{result.rounds.length > 1 ? 's' : ''} -

-
- {result.rounds.flatMap(r => - r.log.map((line, i) => { - const cls = line.includes('CRITIQUE') ? 'log-crit' - : line.includes('esquive') ? 'log-crit' - : line.includes('HP') ? 'log-system' - : i === 0 ? 'log-player' - : 'log-monster'; - return
[T{r.round}] {line}
; - }) - )} - {won - ?
══ Victoire ══
- :
══ Défaite ══
- } -
-
- ); -} - -function MultiCombatView({ result }: { result: MultiCombatResult }) { - const t = result.totals; - return ( -
-
-
0 ? '#e84040' : '#3ddc84' }}> - {t.losses > 0 ? : } - {result.count} combat{result.count > 1 ? 's' : ''} — {t.wins}V / {t.losses}D -
-
- +{t.xp} XP +{t.gold} Or - {t.goldLost > 0 && −{t.goldLost} Or} -
- {t.levelsGained > 0 && ( -
- 🎉 {t.levelsGained} level up{t.levelsGained > 1 ? 's' : ''} ! -
- )} - {t.loot.length > 0 && ( -
- 🎁 Loot : {t.loot.reduce((sum, l) => sum + l.quantity, 0)} matériaux -
- )} - {t.losses > 0 && ( -
- Série interrompue par une défaite -
- )} -
-
- ); -} - -function HistoryEntry({ h }: { h: CombatLog }) { - return ( -
- - {h.winner === 'player' ? '✓' : '✗'} {h.monster.name} - - - {h.winner === 'player' - ? `+${h.xpEarned}xp +${h.goldEarned}or${h.lootQuantity > 0 ? ` 🎁×${h.lootQuantity}` : ''}` - : `${h.totalRounds} tours` - } - -
- ); -} +import { MonsterCard } from '../components/MonsterCard'; +import { CombatLogView, MultiCombatView, HistoryEntry } from '../components/CombatViews'; export function CombatPage() { const qc = useQueryClient(); diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index e6993fc..58b5e50 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -4,76 +4,13 @@ import { characterApi, itemApi } from '../api/endpoints'; import { api } from '../api/client'; import { Bar } from '../components/Bar'; import { Onboarding } from '../components/Onboarding'; +import { CreateCharacter } from '../components/CreateCharacter'; import { STAT_LABELS as STAT_LABELS_MAP } from '../constants'; import { Zap, Heart, Star, Coins, Sword, Shield, BedDouble } from 'lucide-react'; const STATS = ['force', 'agilite', 'intelligence', 'chance', 'vitalite'] as const; const STAT_LABELS = STAT_LABELS_MAP; -function CreateCharacter() { - const qc = useQueryClient(); - const [name, setName] = useState(''); - const [pts, setPts] = useState>({ force:1, agilite:1, intelligence:1, chance:1, vitalite:1 }); - const used = Object.values(pts).reduce((a, b) => a + b, 0) - 5; - const remaining = 5 - used; - - const mut = useMutation({ - mutationFn: () => characterApi.create(name, pts), - onSuccess: () => qc.invalidateQueries({ queryKey: ['character'] }), - }); - - const adjust = (stat: string, delta: number) => { - const next = (pts[stat] ?? 1) + delta; - if (next < 1 || next > 10) return; - if (delta > 0 && remaining <= 0) return; - setPts(p => ({ ...p, [stat]: next })); - }; - - return ( -
-
-

Créer ton personnage

-

- {remaining > 0 ? `${remaining} point${remaining > 1 ? 's' : ''} à répartir` : 'Tous les points répartis'} -

- - setName(e.target.value)} - style={{ marginBottom: '1rem' }} - maxLength={30} - /> - -
- {STATS.map(s => ( -
- {STAT_LABELS[s]} -
- - {pts[s]} - -
-
- ))} -
- - - - {mut.isError &&

{(mut.error as Error).message}

} -
-
- ); -} - function StatDistributor({ char }: { char: any }) { const qc = useQueryClient(); const [pts, setPts] = useState>({ force: 0, agilite: 0, intelligence: 0, chance: 0, vitalite: 0 }); diff --git a/frontend/src/pages/GuidePage.tsx b/frontend/src/pages/GuidePage.tsx index 32d4b43..dea1447 100644 --- a/frontend/src/pages/GuidePage.tsx +++ b/frontend/src/pages/GuidePage.tsx @@ -3,7 +3,8 @@ import { useNavigate } from 'react-router-dom'; import type { Monster, Item, Recipe } from '../api/types'; import { Swords, Shield, Map as MapIcon, Hammer, ShoppingBag, BookOpen, Sparkles, Search, Gamepad2 } from 'lucide-react'; import { useGuideData } from '../hooks/useGuideData'; -import { RARITY_COLORS, RARITY_LABELS, FORGE_TABLE, ZONE_INFO } from '../constants'; +import { RARITY_COLORS, FORGE_TABLE, ZONE_INFO } from '../constants'; +import { RarityBadge } from '../components/RarityBadge'; const ZONES = [ { id: 'marais', ...ZONE_INFO.marais, desc: 'Zone de départ. Monstres niv. 1-9. Terre de boue et de brume.' }, @@ -23,17 +24,6 @@ const TABS = [ // ── Components ── -function RarityBadge({ rarity }: { rarity: string }) { - return ( - - {RARITY_LABELS[rarity] ?? rarity} - - ); -} - function StatBar({ label, value, max, color }: { label: string; value: number; max: number; color: string }) { return (