All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 3m4s
Store refactoring (2138 → 1651 lines, 23% reduction): - Extract computed-stats.ts with 18 utility functions - Extract navigation-slice.ts for floor navigation actions - Extract study-slice.ts for study-related actions - Move fmt/fmtDec to computed-stats, re-export from formatting Page refactoring (2554 → 1695 lines, 34% reduction): - Use existing SpireTab component instead of inline render - Extract ActionButtons component - Extract CalendarDisplay component - Extract CraftingProgress component - Extract StudyProgress component - Extract ManaDisplay component - Extract TimeDisplay component - Create tabs/index.ts for cleaner exports This improves code organization and makes the codebase more maintainable.
162 lines
6.1 KiB
TypeScript
162 lines
6.1 KiB
TypeScript
'use client';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { Target, FlaskConical, Sparkles, Play, Pause, X } from 'lucide-react';
|
|
import { fmt } from '@/lib/game/store';
|
|
import { formatStudyTime } from '@/lib/game/formatting';
|
|
import type { EquipmentInstance, EnchantmentDesign } from '@/lib/game/types';
|
|
|
|
interface CraftingProgressProps {
|
|
designProgress: { designId: string; progress: number; required: number } | null;
|
|
preparationProgress: { equipmentInstanceId: string; progress: number; required: number; manaCostPaid: number } | null;
|
|
applicationProgress: { equipmentInstanceId: string; designId: string; progress: number; required: number; manaPerHour: number; paused: boolean } | null;
|
|
equipmentInstances: Record<string, EquipmentInstance>;
|
|
enchantmentDesigns: EnchantmentDesign[];
|
|
cancelDesign: () => void;
|
|
cancelPreparation: () => void;
|
|
pauseApplication: () => void;
|
|
resumeApplication: () => void;
|
|
cancelApplication: () => void;
|
|
}
|
|
|
|
export function CraftingProgress({
|
|
designProgress,
|
|
preparationProgress,
|
|
applicationProgress,
|
|
equipmentInstances,
|
|
enchantmentDesigns,
|
|
cancelDesign,
|
|
cancelPreparation,
|
|
pauseApplication,
|
|
resumeApplication,
|
|
cancelApplication,
|
|
}: CraftingProgressProps) {
|
|
const progressSections: React.ReactNode[] = [];
|
|
|
|
// Design progress
|
|
if (designProgress) {
|
|
const progressPct = Math.min(100, (designProgress.progress / designProgress.required) * 100);
|
|
progressSections.push(
|
|
<div key="design" className="p-3 rounded border border-cyan-600/50 bg-cyan-900/20">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<Target className="w-4 h-4 text-cyan-400" />
|
|
<span className="text-sm font-semibold text-cyan-300">
|
|
Designing Enchantment
|
|
</span>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-6 w-6 p-0 text-gray-400 hover:text-white"
|
|
onClick={cancelDesign}
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
<Progress value={progressPct} className="h-2 bg-gray-800" />
|
|
<div className="flex justify-between text-xs text-gray-400 mt-1">
|
|
<span>{formatStudyTime(designProgress.progress)} / {formatStudyTime(designProgress.required)}</span>
|
|
<span>Design Time</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Preparation progress
|
|
if (preparationProgress) {
|
|
const progressPct = Math.min(100, (preparationProgress.progress / preparationProgress.required) * 100);
|
|
const instance = equipmentInstances[preparationProgress.equipmentInstanceId];
|
|
progressSections.push(
|
|
<div key="prepare" className="p-3 rounded border border-green-600/50 bg-green-900/20">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<FlaskConical className="w-4 h-4 text-green-400" />
|
|
<span className="text-sm font-semibold text-green-300">
|
|
Preparing {instance?.name || 'Equipment'}
|
|
</span>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-6 w-6 p-0 text-gray-400 hover:text-white"
|
|
onClick={cancelPreparation}
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
<Progress value={progressPct} className="h-2 bg-gray-800" />
|
|
<div className="flex justify-between text-xs text-gray-400 mt-1">
|
|
<span>{formatStudyTime(preparationProgress.progress)} / {formatStudyTime(preparationProgress.required)}</span>
|
|
<span>Mana spent: {fmt(preparationProgress.manaCostPaid)}</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Application progress
|
|
if (applicationProgress) {
|
|
const progressPct = Math.min(100, (applicationProgress.progress / applicationProgress.required) * 100);
|
|
const instance = equipmentInstances[applicationProgress.equipmentInstanceId];
|
|
const design = enchantmentDesigns.find(d => d.id === applicationProgress.designId);
|
|
progressSections.push(
|
|
<div key="enchant" className="p-3 rounded border border-amber-600/50 bg-amber-900/20">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<Sparkles className="w-4 h-4 text-amber-400" />
|
|
<span className="text-sm font-semibold text-amber-300">
|
|
Enchanting {instance?.name || 'Equipment'}
|
|
</span>
|
|
</div>
|
|
<div className="flex gap-1">
|
|
{applicationProgress.paused ? (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-6 w-6 p-0 text-green-400 hover:text-green-300"
|
|
onClick={resumeApplication}
|
|
>
|
|
<Play className="w-4 h-4" />
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-6 w-6 p-0 text-yellow-400 hover:text-yellow-300"
|
|
onClick={pauseApplication}
|
|
>
|
|
<Pause className="w-4 h-4" />
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-6 w-6 p-0 text-gray-400 hover:text-white"
|
|
onClick={cancelApplication}
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<Progress value={progressPct} className="h-2 bg-gray-800" />
|
|
<div className="flex justify-between text-xs text-gray-400 mt-1">
|
|
<span>{formatStudyTime(applicationProgress.progress)} / {formatStudyTime(applicationProgress.required)}</span>
|
|
<span>Mana/hr: {fmt(applicationProgress.manaPerHour)}</span>
|
|
</div>
|
|
{design && (
|
|
<div className="text-xs text-amber-400/70 mt-1">
|
|
Applying: {design.name}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return progressSections.length > 0 ? (
|
|
<div className="space-y-2">
|
|
{progressSections}
|
|
</div>
|
|
) : null;
|
|
}
|