Files
TetaRdPG/frontend/src/components/GuideDrawer.tsx
Tetardtek 9eff6d541e
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 34s
refacto: découpage composants — 5 extractions
- 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/
2026-03-24 23:50:55 +01:00

186 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useRef } from 'react';
import { Search, X } from 'lucide-react';
import { useGuideData } from '../hooks/useGuideData';
import { RARITY_COLORS } from '../constants';
import { RarityDot } from './RarityBadge';
export function GuideDrawer({ open, onClose }: { open: boolean; onClose: () => void }) {
const [search, setSearch] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
const { filteredMonsters, filteredItems, filteredRecipes, matMap, q } = useGuideData(search);
useEffect(() => {
if (open) {
setSearch('');
setTimeout(() => inputRef.current?.focus(), 100);
}
}, [open]);
// Escape to close
useEffect(() => {
if (!open) return;
const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [open, onClose]);
if (!open) return null;
const hasResults = q && (filteredMonsters.length > 0 || filteredItems.length > 0 || filteredRecipes.length > 0);
const noResults = q && !hasResults;
const totalResults = q ? filteredMonsters.length + filteredItems.length + filteredRecipes.length : 0;
return (
<>
{/* Backdrop */}
<div
onClick={onClose}
style={{
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)',
zIndex: 90, transition: 'opacity 0.2s',
}}
/>
{/* Drawer */}
<div style={{
position: 'fixed', top: 0, right: 0, bottom: 0, width: 380,
background: '#0d0f14', borderLeft: '1px solid #2a3448',
zIndex: 100, display: 'flex', flexDirection: 'column',
animation: 'slideIn 0.2s ease-out',
}}>
{/* Header */}
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '12px 16px', borderBottom: '1px solid #2a3448',
}}>
<span style={{ fontWeight: 700, color: '#f4c94e', fontSize: 14 }}>📖 Guide rapide</span>
<button onClick={onClose} style={{ background: 'none', border: 'none', color: '#6b7a99', cursor: 'pointer', padding: 4 }}>
<X size={16} />
</button>
</div>
{/* Search */}
<div style={{ padding: '12px 16px', borderBottom: '1px solid #1e2535' }}>
<div style={{ position: 'relative' }}>
<Search size={13} style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)', color: '#6b7a99' }} />
<input
ref={inputRef}
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Monstre, item, matériau, recette…"
style={{
width: '100%', padding: '8px 10px 8px 30px', fontSize: 12,
background: '#1e2535', border: '1px solid #2a3448', borderRadius: 6,
color: '#dce4f0', outline: 'none', boxSizing: 'border-box',
}}
/>
</div>
{q && (
<div style={{ fontSize: 10, color: '#6b7a99', marginTop: 4 }}>
{totalResults} résultat{totalResults !== 1 ? 's' : ''}
</div>
)}
</div>
{/* Results */}
<div style={{ flex: 1, overflowY: 'auto', padding: '8px 16px' }}>
{!q && (
<div style={{ textAlign: 'center', padding: '2rem 0', color: '#6b7a99', fontSize: 12 }}>
Tapez pour rechercher dans le guide
</div>
)}
{noResults && (
<div style={{ textAlign: 'center', padding: '2rem 0', color: '#6b7a99', fontSize: 12 }}>
Aucun résultat pour « {search} »
</div>
)}
{/* Monstres */}
{q && filteredMonsters.length > 0 && (
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 10, fontWeight: 700, color: '#6b7a99', textTransform: 'uppercase', marginBottom: 6 }}>
Monstres ({filteredMonsters.length})
</div>
{filteredMonsters.map(m => (
<div key={m.id} style={{ padding: '6px 0', borderBottom: '1px solid #1e2535', fontSize: 12 }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: '#dce4f0', fontWeight: 600 }}>{m.name}</span>
<span style={{ color: '#6b7a99', fontSize: 10 }}>Niv. {m.minLevel}{m.maxLevel}</span>
</div>
<div style={{ color: '#6b7a99', fontSize: 10, marginTop: 2 }}>
{m.hp} {m.attack} 🛡{m.defense} · {m.xpReward}xp · 💰{m.goldMin}{m.goldMax}
{m.dropMaterialId && matMap.get(m.dropMaterialId) && (
<span style={{ color: '#f4c94e' }}> · 🎁 {matMap.get(m.dropMaterialId)!.name}</span>
)}
</div>
</div>
))}
</div>
)}
{/* Items */}
{q && filteredItems.length > 0 && (
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 10, fontWeight: 700, color: '#6b7a99', textTransform: 'uppercase', marginBottom: 6 }}>
Équipement ({filteredItems.length})
</div>
{filteredItems.map(item => (
<div key={item.id} style={{ padding: '6px 0', borderBottom: '1px solid #1e2535', fontSize: 12 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ color: RARITY_COLORS[item.rarity], fontWeight: 600 }}>
<RarityDot rarity={item.rarity} />
{item.type === 'weapon' ? '⚔️' : item.type === 'armor' ? '🛡️' : '🧪'} {item.name}
</span>
</div>
<div style={{ color: '#6b7a99', fontSize: 10, marginTop: 2 }}>
{item.attackBonus > 0 && `ATK+${item.attackBonus} `}
{item.defenseBonus > 0 && `DEF+${item.defenseBonus} `}
{item.agiliteBonus > 0 && `AGI+${item.agiliteBonus} `}
{item.intelligenceBonus > 0 && `INT+${item.intelligenceBonus} `}
{(item as any).buyPrice > 0 ? `· 💰${(item as any).buyPrice}` : '· 🔨 Craft'}
</div>
</div>
))}
</div>
)}
{/* Recettes */}
{q && filteredRecipes.length > 0 && (
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 10, fontWeight: 700, color: '#6b7a99', textTransform: 'uppercase', marginBottom: 6 }}>
Recettes ({filteredRecipes.length})
</div>
{filteredRecipes.map(r => (
<div key={r.id} style={{ padding: '6px 0', borderBottom: '1px solid #1e2535', fontSize: 12 }}>
<div style={{ color: RARITY_COLORS[r.resultItem?.rarity] ?? '#dce4f0', fontWeight: 600 }}>
<RarityDot rarity={r.resultItem?.rarity ?? 'common'} />
{r.resultItem?.name ?? r.name}
</div>
<div style={{ color: '#6b7a99', fontSize: 10, marginTop: 2 }}>
{r.ingredients.map((ing, i) => (
<span key={i}>{i > 0 ? ' + ' : ''}{ing.quantity}× {matMap.get(ing.materialId)?.name ?? '???'}</span>
))}
<span> · {r.craftDurationSeconds}s · {r.enduranceCost}</span>
</div>
</div>
))}
</div>
)}
</div>
{/* Footer */}
<div style={{
padding: '10px 16px', borderTop: '1px solid #2a3448',
textAlign: 'center',
}}>
<a href="/guide" style={{ fontSize: 11, color: '#6b7a99', textDecoration: 'none' }}>
Ouvrir le guide complet
</a>
</div>
</div>
</>
);
}