import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { getTranslatedText } from '../../utils/i18nUtils'; import api from '../../services/api'; import { GameModal } from './GameModal'; import { GameProgressBar } from '../common/GameProgressBar'; import { GameButton } from '../common/GameButton'; import './CharacterSheet.css'; interface CharacterSheetProps { onClose: () => void; onSpendPoint: (stat: string) => void; } interface DerivedStats { attack_power: number; crit_chance: number; crit_damage: number; dodge_chance: number; flee_chance_base: number; max_hp: number; max_stamina: number; total_armor: number; armor_reduction: number; block_chance: number; status_resistance: number; item_effectiveness: number; xp_bonus: number; loot_quality: number; crafting_bonus: number; carry_weight: number; weapon_damage_min: number; weapon_damage_max: number; has_shield: boolean; } interface SkillData { id: string; name: any; description: any; icon: string; stat_requirement: string; stat_threshold: number; level_requirement: number; cooldown: number; stamina_cost: number; unlocked: boolean; } interface PerkData { id: string; name: any; description: any; icon: string; requirements: Record; effects: Record; meets_requirements: boolean; owned: boolean; } interface CharacterSheetData { base_stats: { strength: number; agility: number; endurance: number; intellect: number; unspent_points: number; stat_cap: number; }; derived_stats: DerivedStats; skills: SkillData[]; perks: { available_points: number; total_points: number; used_points: number; all_perks: PerkData[]; }; character: { name: string; level: number; xp: number; hp: number; max_hp: number; stamina: number; max_stamina: number; avatar_data?: string; }; } const STAT_ICONS: Record = { strength: '💪', agility: '🏃', endurance: '🫀', intellect: '🧠', }; const STAT_COLORS: Record = { strength: '#e74c3c', agility: '#2ecc71', endurance: '#f39c12', intellect: '#3498db', }; export function CharacterSheet({ onClose, onSpendPoint }: CharacterSheetProps) { const { t } = useTranslation(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState<'stats' | 'skills' | 'perks'>('stats'); const [selectingPerk, setSelectingPerk] = useState(false); const fetchSheet = async () => { try { const res = await api.get('/api/game/character-sheet'); setData(res.data); } catch (err) { console.error('Failed to fetch character sheet:', err); } finally { setLoading(false); } }; useEffect(() => { fetchSheet(); }, []); const handleSpendPoint = async (stat: string) => { onSpendPoint(stat); // Refetch after a short delay to get updated derived stats setTimeout(fetchSheet, 500); }; const handleSelectPerk = async (perkId: string) => { setSelectingPerk(true); try { await api.post(`/api/game/select_perk?perk_id=${perkId}`); await fetchSheet(); } catch (err: any) { console.error('Failed to select perk:', err.response?.data?.detail || err.message); } finally { setSelectingPerk(false); } }; if (loading || !data) { return (
{t('common.loading', 'Loading...')}
); } const { base_stats, derived_stats, skills, perks, character } = data; const renderStatsTab = () => (
{/* Base Stats Section */}

{t('characterSheet.baseStats', 'Base Stats')}

{/* Vitals Moved Here */}
{base_stats.unspent_points > 0 && (
{base_stats.unspent_points} {t('characterSheet.pointsAvailable', 'points available')}
)}
{(['strength', 'agility', 'endurance', 'intellect'] as const).map(stat => (
{STAT_ICONS[stat]} {t(`stats.${stat}Full`)}
{base_stats.unspent_points > 0 && base_stats[stat] < base_stats.stat_cap && ( )}
))}
{/* Derived Stats Section */}

{t('characterSheet.derivedStats', 'Derived Stats')}

); const renderSkillsTab = () => { const grouped: Record = { strength: [], agility: [], endurance: [], intellect: [], }; skills.forEach(s => { if (grouped[s.stat_requirement]) { grouped[s.stat_requirement].push(s); } }); return (
{Object.entries(grouped).map(([stat, statSkills]) => (

{STAT_ICONS[stat]} {t(`stats.${stat}Full`)}

{statSkills.map(skill => (
{skill.icon} {getTranslatedText(skill.name)} {skill.unlocked ? ( ) : ( 🔒 )}

{getTranslatedText(skill.description)}

{skill.stamina_cost} 🔄 {skill.cooldown}t {t(`stats.${skill.stat_requirement}`)}: {skill.stat_threshold} {t('stats.level')}: {skill.level_requirement}
))}
))}
); }; const renderPerksTab = () => (
{t('characterSheet.perkPoints', 'Perk Points')}: {perks.available_points} / {perks.total_points} ({t('characterSheet.nextPerkAt', 'Next at Lv')} {((perks.used_points + perks.available_points + 1) * 5)})
{perks.all_perks.map(perk => { const canSelect = perk.meets_requirements && !perk.owned && perks.available_points > 0; return (
{perk.icon}
{getTranslatedText(perk.name)}

{getTranslatedText(perk.description)}

{perk.owned ? ( {t('characterSheet.owned', 'Owned')} ) : canSelect ? ( handleSelectPerk(perk.id)} disabled={selectingPerk} > {t('characterSheet.select', 'Select')} ) : ( 🔒 )}
{Object.entries(perk.requirements).map(([key, val]) => { const isMax = key.endsWith('_max'); const baseKey = isMax ? key.replace('_max', '') : key; const displayKey = ['strength', 'agility', 'endurance', 'intellect', 'level'].includes(baseKey) ? t(`stats.${baseKey}`) : baseKey; const currentVal = baseKey === 'level' ? character.level : ((data.base_stats as any)[baseKey] || 0); const isMet = isMax ? currentVal <= val : currentVal >= val; return ( {displayKey} {isMax ? '≤' : '≥'} {val} ); })}
); })}
); const modalTitle = (
{character.name} — Lv. {character.level}
); return ( {/* Tab Navigation */}
{/* Tab Content */}
{activeTab === 'stats' && renderStatsTab()} {activeTab === 'skills' && renderSkillsTab()} {activeTab === 'perks' && renderPerksTab()}
); } function DerivedStatRow({ icon, label, value }: { icon: string; label: string; value: any }) { return (
{icon} {label} {value}
); }