Phase 3: Split CraftingTab.tsx into crafting stage components

This commit is contained in:
Unknown
2026-04-24 13:05:12 +02:00
parent c46981d81b
commit a528feb8e2
7 changed files with 1067 additions and 843 deletions
@@ -0,0 +1,200 @@
'use client';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import { Package, Sparkles, Trash2, Anvil } from 'lucide-react';
import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recipes';
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
import { fmt, type GameStore } from '@/lib/game/store';
export interface EquipmentCrafterProps {
store: GameStore;
}
export function EquipmentCrafter({ store }: EquipmentCrafterProps) {
const lootInventory = store.lootInventory;
const equipmentCraftingProgress = store.equipmentCraftingProgress;
const rawMana = store.rawMana;
const currentAction = store.currentAction;
const startCraftingEquipment = store.startCraftingEquipment;
const cancelEquipmentCrafting = store.cancelEquipmentCrafting;
const deleteMaterial = store.deleteMaterial;
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* Blueprint Selection */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 text-sm flex items-center gap-2">
<Anvil className="w-4 h-4" />
Available Blueprints
</CardTitle>
</CardHeader>
<CardContent>
{equipmentCraftingProgress ? (
<div className="space-y-3">
<div className="text-sm text-gray-400">
Crafting: {CRAFTING_RECIPES[equipmentCraftingProgress.blueprintId]?.name}
</div>
<Progress value={(equipmentCraftingProgress.progress / equipmentCraftingProgress.required) * 100} className="h-3" />
<div className="flex justify-between text-xs text-gray-400">
<span>{equipmentCraftingProgress.progress.toFixed(1)}h / {equipmentCraftingProgress.required.toFixed(1)}h</span>
<span>Mana spent: {fmt(equipmentCraftingProgress.manaSpent)}</span>
</div>
<Button size="sm" variant="outline" onClick={cancelEquipmentCrafting}>Cancel</Button>
</div>
) : (
<ScrollArea className="h-64">
<div className="space-y-2">
{lootInventory.blueprints.length === 0 ? (
<div className="text-center text-gray-400 py-4">
<Package className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p>No blueprints discovered yet.</p>
<p className="text-xs mt-1">Defeat guardians to find blueprints!</p>
</div>
) : (
lootInventory.blueprints.map(bpId => {
const recipe = CRAFTING_RECIPES[bpId];
if (!recipe) return null;
const { canCraft, missingMaterials, missingMana } = canCraftRecipe(
recipe,
lootInventory.materials,
rawMana
);
const rarityStyle = RARITY_COLORS[recipe.rarity];
return (
<div
key={bpId}
className="p-3 rounded border bg-gray-800/50"
style={{ borderColor: rarityStyle?.color }}
>
<div className="flex justify-between items-start mb-2">
<div>
<div className="font-semibold" style={{ color: rarityStyle?.color }}>
{recipe.name}
</div>
<div className="text-xs text-gray-400 capitalize">{recipe.rarity}</div>
</div>
<Badge variant="outline" className="text-xs">
{recipe.equipmentTypeId ? 'Equipment' : 'Other'}
</Badge>
</div>
<div className="text-xs text-gray-400 mb-2">{recipe.description}</div>
<Separator className="bg-gray-700 my-2" />
<div className="text-xs space-y-1">
<div className="text-gray-500">Materials:</div>
{Object.entries(recipe.materials).map(([matId, amount]) => {
const available = lootInventory.materials[matId] || 0;
const matDrop = LOOT_DROPS[matId];
const hasEnough = available >= amount;
return (
<div key={matId} className="flex justify-between">
<span>{matDrop?.name || matId}</span>
<span className={hasEnough ? 'text-green-400' : 'text-red-400'}>
{available} / {amount}
</span>
</div>
);
})}
<div className="flex justify-between mt-2">
<span>Mana Cost:</span>
<span className={rawMana >= recipe.manaCost ? 'text-green-400' : 'text-red-400'}>
{fmt(recipe.manaCost)}
</span>
</div>
<div className="flex justify-between">
<span>Craft Time:</span>
<span>{recipe.craftTime}h</span>
</div>
</div>
<Button
className="w-full mt-3"
size="sm"
disabled={!canCraft || currentAction === 'craft'}
onClick={() => startCraftingEquipment(bpId)}
>
{canCraft ? 'Craft Equipment' : 'Missing Resources'}
</Button>
</div>
);
})
)}
</div>
</ScrollArea>
)}
</CardContent>
</Card>
{/* Materials Inventory */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 text-sm flex items-center gap-2">
<Package className="w-4 h-4" />
Materials ({Object.values(lootInventory.materials).reduce((a, b) => a + b, 0)})
</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-64">
{Object.keys(lootInventory.materials).length === 0 ? (
<div className="text-center text-gray-400 py-4">
<Sparkles className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p>No materials collected yet.</p>
<p className="text-xs mt-1">Defeat floors to gather materials!</p>
</div>
) : (
<div className="grid grid-cols-2 gap-2">
{Object.entries(lootInventory.materials).map(([matId, count]) => {
if (count <= 0) return null;
const drop = LOOT_DROPS[matId];
if (!drop) return null;
const rarityStyle = RARITY_COLORS[drop.rarity];
return (
<div
key={matId}
className="p-2 rounded border bg-gray-800/50 group relative"
style={{ borderColor: rarityStyle?.color }}
>
<div className="flex items-start justify-between">
<div>
<div className="text-sm font-semibold" style={{ color: rarityStyle?.color }}>
{drop.name}
</div>
<div className="text-xs text-gray-400">x{count}</div>
</div>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0 opacity-0 group-hover:opacity-100 text-red-400 hover:text-red-300 hover:bg-red-900/20"
onClick={() => deleteMaterial(matId, count)}
>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
);
})}
</div>
)}
</ScrollArea>
</CardContent>
</Card>
</div>
);
}