Files
Mana-Loop/src/components/game/tabs/SkillRow.tsx
T
Refactoring Agent f0ab3ca3ce
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m10s
refactor: extract components from SkillsTab.tsx to reduce below 400 lines
2026-05-01 16:41:29 +02:00

232 lines
7.8 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.
// ─── Skill Row Component ───────────────────────────────────────────────────
// Individual skill row for the Skills tab - extracted from SkillsTab for modularity
'use client';
import { useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { ChevronRight } from 'lucide-react';
import type { SkillUpgradeChoice } from '@/lib/game/types';
import { ELEMENTS } from '@/lib/game/constants';
import { formatStudyTime } from '@/lib/game/formatting';
import { getUpgradesForSkillAtMilestone, getNextTierSkill, getTierMultiplier, SKILL_EVOLUTION_PATHS } from '@/lib/game/skill-evolution';
import type { ComputedEffects } from '@/lib/game/upgrade-effects.types';
import { SPECIAL_EFFECTS, hasSpecial } from '@/lib/game/special-effects';
import { MilestoneProgress } from './MilestoneProgress';
import { SkillMultipliers } from './SkillMultipliers';
type StudyTarget = { type: 'skill' | 'spell'; id: string; progress: number; required: number; } | null;
interface SkillRowProps {
skillId: string;
def: {
name: string;
desc: string;
cat: string;
max: number;
studyTime: number;
base: number;
cost?: { type: 'element'; element: string; amount: number } | { type: 'raw'; amount: number };
req?: Record<string, number>;
};
level: number;
maxed: boolean;
isStudying: boolean;
tierMultiplier: number;
skillDisplayName: string;
selectedUpgrades: string[];
selectedL5: string[];
selectedL10: string[];
prereqMet: boolean;
canStudy: boolean;
isParallelStudy: boolean;
canParallelStudy: boolean;
canTierUp: boolean;
hasInsufficientMana: boolean;
currentStudyTarget: StudyTarget;
milestoneInfo: { milestone: 5 | 10; hasUpgrades: boolean; selectedCount: number } | null;
upgradeEffects: ComputedEffects;
// Costs and times
cost: number;
additionalCost?: { type: 'element'; element: string; amount: number };
effectiveStudyTime: number;
costMult: number;
speedMult: number;
// Callbacks
onStudy: (skillId: string) => void;
onParallelStudy: (skillId: string) => void;
onCancelStudy: (skillId: string) => void;
onUpgradeDialogOpen: (skillId: string, milestone: 5 | 10) => void;
onTierUp: (skillId: string) => void;
onShowToast: (type: 'info' | 'error', title: string, description: string) => void;
tierUpLabel?: string;
}
export function SkillRow(props: SkillRowProps) {
const {
skillId,
def,
level,
maxed,
isStudying,
tierMultiplier,
skillDisplayName,
selectedUpgrades,
selectedL5,
selectedL10,
prereqMet,
canStudy,
isParallelStudy,
canParallelStudy,
canTierUp,
hasInsufficientMana,
currentStudyTarget,
milestoneInfo,
upgradeEffects,
cost,
additionalCost,
effectiveStudyTime,
costMult,
speedMult,
onStudy,
onParallelStudy,
onCancelStudy,
onUpgradeDialogOpen,
onTierUp,
} = props;
return (
<div
key={skillId}
className={`flex flex-col sm:flex-row sm:items-center justify-between p-3 rounded border gap-2 ${
isStudying ? 'border-purple-500 bg-purple-900/20' :
milestoneInfo ? 'border-amber-500/50 bg-amber-900/10' :
'border-gray-700 bg-gray-800/30'
}`}
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-semibold text-sm">{skillDisplayName}</span>
{level > 0 && <span className="text-purple-400 text-sm">Lv.{level}</span>}
{selectedUpgrades.length > 0 && (
<div className="flex gap-1">
{selectedL5.length > 0 && (
<Badge className="bg-amber-700/50 text-amber-200 text-xs">L5: {selectedL5.length}</Badge>
)}
{selectedL10.length > 0 && (
<Badge className="bg-purple-700/50 text-purple-200 text-xs">L10: {selectedL10.length}</Badge>
)}
</div>
)}
</div>
<div className="text-xs text-gray-400 italic">{def.desc}{level > 0 && tierMultiplier !== 1 && ` (Tier ${tierMultiplier}x effect)`}</div>
{!prereqMet && def.req && (
<div className="text-xs text-red-400 mt-1">
Requires: {Object.entries(def.req).map(([r, rl]) => `${r} Lv.${rl}`).join(', ')}
</div>
)}
<SkillMultipliers
effectiveStudyTime={effectiveStudyTime}
speedMult={speedMult}
costMult={costMult}
cost={cost}
additionalCost={additionalCost}
/>
{hasInsufficientMana && (
<div className="text-xs text-red-400 mt-1">
Insufficient mana! Need {cost} mana to study.
</div>
)}
<MilestoneProgress milestoneInfo={milestoneInfo} />
</div>
<div className="flex items-center gap-3 flex-wrap sm:flex-nowrap">
{/* Level dots */}
<div className="flex gap-1 shrink-0">
{Array.from({ length: def.max }).map((_, i) => (
<div
key={i}
className={`w-2 h-2 rounded-full border ${
i < level ? 'bg-purple-500 border-purple-400' :
i === 4 || i === 9 ? 'border-amber-500' :
'border-gray-600'
}`}
/>
))}
</div>
{isStudying ? (
<div className="text-xs text-purple-400">
{formatStudyTime(currentStudyTarget?.progress || 0)}/{formatStudyTime(def.studyTime * (level > 1 ? level : 1))}
</div>
) : milestoneInfo ? (
<Button
size="sm"
className="bg-amber-600 hover:bg-amber-700"
onClick={() => onUpgradeDialogOpen(skillId, milestoneInfo.milestone)}
>
Choose Upgrades
</Button>
) : canTierUp ? (
<Button
size="sm"
className="bg-purple-600 hover:bg-purple-700"
onClick={() => onTierUp(skillId)}
>
Tier Up
</Button>
) : maxed ? (
<Badge className="bg-green-900/50 text-green-300">Maxed</Badge>
) : (
<div className="flex gap-1">
<Button
size="sm"
variant={canStudy ? 'default' : 'outline'}
disabled={!canStudy}
className={canStudy ? 'bg-purple-600 hover:bg-purple-700' : 'opacity-50'}
onClick={() => {
if (cost > 0) {
onStudy(skillId);
}
}}
>
Study ({cost}{additionalCost && additionalCost.type === 'element' && ` + ${additionalCost.amount} ${ELEMENTS[additionalCost.element]?.sym}`}
</Button>
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.PARALLEL_STUDY) &&
currentStudyTarget &&
!isParallelStudy &&
canParallelStudy &&
canStudy && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
variant="outline"
className="border-cyan-500 text-cyan-400 hover:bg-cyan-900/30"
onClick={() => {
if (cost > 0) {
onParallelStudy(skillId);
}
}}
>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Study in parallel (50% speed)</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
)}
</div>
</div>
);
}