Files
Mana-Loop/src/components/game/tabs/SkillsTab.tsx
T
Refactoring Agent 837d963b63
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 30m15s
fix: split SpireTab.tsx to 395 lines, remove require() imports, import from data modules; complete store migration
2026-05-04 13:36:10 +02:00

253 lines
9.4 KiB
TypeScript
Executable File

// ─── Skills Tab ───────────────────────────────────────────────────────────────
// SkillsTab - Displays all skills organized by category
// Refactored: extracted components for better modularity (reduced from 400 lines)
'use client';
import { useState, useCallback } from 'react';
import {
SKILLS_DEF,
SKILL_CATEGORIES,
getStudySpeedMultiplier,
getStudyCostMultiplier,
} from '@/lib/game/constants';
import {
SKILL_EVOLUTION_PATHS,
getUpgradesForSkillAtMilestone,
getNextTierSkill,
getTierMultiplier,
} from '@/lib/game/skill-evolution';
import { getUnifiedEffects } from '@/lib/game/effects';
import { getAvailableSkillCategories } from '@/lib/game/data/attunements';
import { fmt, fmtDec } from '@/lib/game/stores';
import type { SkillUpgradeChoice } from '@/lib/game/types';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { StudyProgress } from './StudyProgress';
import { UpgradeDialog } from './UpgradeDialog';
import { ConfirmDialog } from '@/components/game/ConfirmDialog';
import { useGameToast } from '@/components/game/GameToast';
import { ELEMENTS } from '@/lib/game/constants';
import { ChevronDown, ChevronRight } from 'lucide-react';
import { SkillRow } from './SkillRow';
import { useSkillUpgradeSelection } from '@/lib/game/hooks/useSkillUpgradeSelection';
import { CategorySkillsList } from './CategorySkillsList';
import { useGameStore, useSkillStore, usePrestigeStore } from '@/lib/game/stores';
export function SkillsTab() {
const showToast = useGameToast();
const [upgradeDialogSkill, setUpgradeDialogSkill] = useState<string | null>(null);
const [upgradeDialogMilestone, setUpgradeDialogMilestone] = useState<5 | 10>(5);
const [collapsedCategories, setCollapsedCategories] = useState<Set<string>>(new Set());
const [cancelStudyConfirm, setCancelStudyConfirm] = useState<{
skillId: string;
skillName: string;
} | null>(null);
const skills = useSkillStore((s) => s.skills);
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
const skillTiers = useSkillStore((s) => s.skillTiers);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const currentStudyTarget = useGameStore((s) => s.currentStudyTarget);
const parallelStudyTarget = useGameStore((s) => s.parallelStudyTarget);
const startStudyingSkill = useSkillStore((s) => s.startStudyingSkill);
const startParallelStudySkill = useSkillStore((s) => s.startParallelStudySkill);
const cancelStudy = useGameStore((s) => s.cancelStudy);
const commitSkillUpgrades = useSkillStore((s) => s.commitSkillUpgrades);
const tierUpSkill = useSkillStore((s) => s.tierUpSkill);
const studySpeedMult = getStudySpeedMultiplier({ skills, prestigeUpgrades, skillUpgrades, skillTiers });
const upgradeEffects = getUnifiedEffects({ skillUpgrades, skillTiers, equippedInstances: {}, equipmentInstances: {} });
// Upgrade selection hook
const {
pendingSelections,
setPendingSelections,
toggleUpgrade,
handleConfirm: hookHandleConfirm,
handleCancel: hookHandleCancel,
} = useSkillUpgradeSelection();
// Toggle category collapse
const toggleCategory = (categoryId: string) => {
setCollapsedCategories((prev) => {
const newSet = new Set(prev);
if (newSet.has(categoryId)) {
newSet.delete(categoryId);
} else {
newSet.add(categoryId);
}
return newSet;
});
};
// Get upgrade choices for dialog
const getUpgradeChoices = () => {
if (!upgradeDialogSkill)
return { available: [] as SkillUpgradeChoice[], selected: [] as string[] };
const skillDef = SKILLS_DEF[upgradeDialogSkill.includes('_t') ? upgradeDialogSkill.split('_t')[0] : upgradeDialogSkill];
if (!skillDef) return { available: [] as SkillUpgradeChoice[], selected: [] as string[] };
return {
available: getUpgradesForSkillAtMilestone(upgradeDialogSkill, upgradeDialogMilestone),
selected: skillUpgrades[upgradeDialogSkill] || [],
};
};
const { available, selected: alreadySelected } = getUpgradeChoices();
// Handle upgrade dialog confirm
const handleConfirm = () => {
hookHandleConfirm(
upgradeDialogSkill!,
upgradeDialogMilestone,
(skillId: string, selections: string[], milestone: 5 | 10) => {
commitSkillUpgrades(skillId, selections, milestone);
return () => setUpgradeDialogSkill(null);
}
);
};
// Handle upgrade dialog cancel
const handleCancel = () => {
hookHandleCancel(() => setUpgradeDialogSkill(null));
};
// Wrapper for upgrade toggle that matches UpgradeDialog's onToggle signature
const handleUpgradeToggle = useCallback(
(upgradeId: string) => {
toggleUpgrade(upgradeId, available, alreadySelected);
},
[toggleUpgrade, available, alreadySelected]
);
// Handle study start with toast
const handleStartStudying = (skillId: string) => {
const skillDef = SKILLS_DEF[skillId.includes('_t') ? skillId.split('_t')[0] : skillId];
startStudyingSkill(skillId);
showToast('info', 'Study Started', `Studying ${skillDef?.name || 'skill'}...`);
};
// Handle parallel study start with toast
const handleParallelStudy = (skillId: string) => {
const skillDef = SKILLS_DEF[skillId.includes('_t') ? skillId.split('_t')[0] : skillId];
startParallelStudySkill(skillId);
showToast('info', 'Parallel Study Started', `Studying ${skillDef?.name || 'skill'} in parallel (50% speed)...`);
};
// Handle study cancel with confirmation
const handleCancelStudy = () => {
const currentTarget = currentStudyTarget;
if (currentTarget?.type === 'skill') {
const skillDef = SKILLS_DEF[currentTarget.id.includes('_t') ? currentTarget.id.split('_t')[0] : currentTarget.id];
setCancelStudyConfirm({
skillId: currentTarget.id,
skillName: skillDef?.name || 'Unknown Skill',
});
}
};
const confirmCancelStudy = () => {
if (cancelStudyConfirm) {
cancelStudy();
showToast(
'warning',
'Study Cancelled',
`${cancelStudyConfirm.skillName} study cancelled. Progress will be partially saved based on your Knowledge Retention skill.`
);
setCancelStudyConfirm(null);
}
};
// Get available skill categories based on attunements
const attunements = useGameStore((s) => s.attunements);
const availableCategories = getAvailableSkillCategories(attunements || {});
return (
<div className="space-y-4">
{/* Upgrade Selection Dialog */}
<UpgradeDialog
open={!!upgradeDialogSkill}
skillId={upgradeDialogSkill}
milestone={upgradeDialogMilestone}
pendingSelections={pendingSelections.length > 0 ? pendingSelections : alreadySelected}
available={available}
alreadySelected={alreadySelected}
onToggle={handleUpgradeToggle}
onConfirm={handleConfirm}
onCancel={handleCancel}
onOpenChange={(open) => {
if (!open) {
setPendingSelections([]);
setUpgradeDialogSkill(null);
}
}}
/>
{/* Cancel Study Confirmation Dialog */}
{cancelStudyConfirm && (
<ConfirmDialog
open={!!cancelStudyConfirm}
onOpenChange={() => setCancelStudyConfirm(null)}
title="Cancel Studying?"
description={`Cancel studying ${cancelStudyConfirm.skillName}? Progress will be partially saved based on your Knowledge Retention skill.`}
variant="warning"
confirmText="Cancel Study"
onConfirm={confirmCancelStudy}
/>
)}
{/* Current Study Progress */}
{currentStudyTarget && currentStudyTarget.type === 'skill' && (
<Card className="bg-gray-900/80 border-purple-600/50">
<CardContent className="pt-4">
<StudyProgress
currentStudyTarget={currentStudyTarget}
skills={skills}
studySpeedMult={studySpeedMult}
cancelStudy={handleCancelStudy}
/>
</CardContent>
</Card>
)}
{/* Skill Categories */}
{SKILL_CATEGORIES.filter((cat) => availableCategories.includes(cat.id)).map((cat) => (
<CategorySkillsList
key={cat.id}
category={cat}
availableCategories={availableCategories}
isCollapsed={collapsedCategories.has(cat.id)}
onToggleCategory={toggleCategory}
skills={skills}
skillUpgrades={skillUpgrades}
skillTiers={skillTiers}
prestigeUpgrades={prestigeUpgrades}
studySpeedMult={studySpeedMult}
upgradeEffects={upgradeEffects}
currentStudyTarget={currentStudyTarget}
onStartStudying={handleStartStudying}
onParallelStudy={handleParallelStudy}
onCancelStudy={handleCancelStudy}
onOpenUpgradeDialog={(skillId, milestone) => {
setUpgradeDialogSkill(skillId);
setUpgradeDialogMilestone(milestone);
setPendingSelections([]);
}}
onTierUp={(skillId) => tierUpSkill(skillId)}
pendingSelections={pendingSelections}
setPendingSelections={setPendingSelections}
/>
))}
</div>
);
}
SkillsTab.displayName = "SkillsTab";