API/client/src/views/GameInterface/tabs/CraftingTab.jsx

201 lines
6.3 KiB
JavaScript

import React, { useState, useEffect } from "react";
import { useSocket } from "../../../hooks/useSocket";
import GameDataManager from "../../../services/GameDataManager";
import "./styles/CraftingTab.css";
import CategorySelector from "../components/CategorySelector";
import CraftModal from "./components/CraftModal";
import { config } from "../../../config/api";
const CraftingTab = () => {
const { socket } = useSocket();
const [categories, setCategories] = useState([]);
const [recipes, setRecipes] = useState([]);
const [userInventory, setUserInventory] = useState([]);
const [activeCategory, setActiveCategory] = useState("");
const [selectedRecipe, setSelectedRecipe] = useState(null);
const [activeCraft, setActiveCraft] = useState(null);
useEffect(() => {
const manifestCategories = GameDataManager.getRecipeCategories();
setCategories(manifestCategories);
if (manifestCategories.length > 0 && !activeCategory) {
setActiveCategory(manifestCategories[0].id);
}
}, []);
useEffect(() => {
if (activeCategory) {
const filteredRecipes =
GameDataManager.getRecipesByCategory(activeCategory);
setRecipes(filteredRecipes);
}
}, [activeCategory]);
useEffect(() => {
if (!socket) return;
socket.emit("player:get_inventory");
socket.emit("player:check_active_craft");
const handleInventory = (data) => setUserInventory(data);
const handleCraftStarted = (data) => {
const recipeData = GameDataManager.getRecipe(data.recipeId);
const now = Date.now();
const diff = (data.finishAt - now) / 1000;
if (diff <= 0) {
setActiveCraft(null);
return;
}
setActiveCraft({
recipeId: data.recipeId,
name: recipeData?.displayName || data.recipeId,
finishAt: data.finishAt,
totalTime: data.totalTime || recipeData?.time_seconds || diff,
timeLeft: Math.max(0, Math.ceil(diff)),
});
if (recipeData) {
setSelectedRecipe(recipeData);
}
};
const handleCraftSuccess = () => {
setActiveCraft(null);
setSelectedRecipe(null);
socket.emit("player:get_inventory");
};
socket.on("player:inventory_data", handleInventory);
socket.on("player:craft_started", handleCraftStarted);
socket.on("player:craft_success", handleCraftSuccess);
return () => {
socket.off("player:inventory_data", handleInventory);
socket.off("player:craft_started", handleCraftStarted);
socket.off("player:craft_success", handleCraftSuccess);
};
}, [socket]);
useEffect(() => {
if (!activeCraft) return;
const timer = setInterval(() => {
const now = Date.now();
const diff = Math.max(0, Math.ceil((activeCraft.finishAt - now) / 1000));
if (diff <= 0) {
clearInterval(timer);
setActiveCraft(null);
} else {
setActiveCraft((prev) => (prev ? { ...prev, timeLeft: diff } : null));
}
}, 1000);
return () => clearInterval(timer);
}, [activeCraft?.finishAt]);
const getOwnedAmount = (itemId) => {
const item = userInventory.find((i) => (i.itemId || i.id) === itemId);
return item ? item.quantity : 0;
};
const handleStartCrafting = (recipe) => {
if (activeCraft) return;
socket.emit("player:craft_item", { recipeId: recipe.id });
};
return (
<div className="tab-content active">
<div className="crafting-container">
{activeCraft && (
<div className="active-craft-panel">
<div className="craft-info">
<span>
<i className="fas fa-hammer fa-spin"></i> Assembling:{" "}
{activeCraft.name}
</span>
<span className="time-left">{activeCraft.timeLeft}s</span>
</div>
<div className="progress-bar-bg">
<div
className="progress-bar-fill"
style={{
width: `${Math.min(100, ((activeCraft.totalTime - activeCraft.timeLeft) / activeCraft.totalTime) * 100)}%`,
}}
></div>
</div>
</div>
)}
<div className="crafting-header">
<h2>
<i className="fas fa-microchip"></i> Fabrication Unit
</h2>
</div>
<CategorySelector
categories={categories}
activeCategory={activeCategory}
onCategoryChange={setActiveCategory}
/>
<div className="crafting-grid">
{recipes.map((recipe) => {
const isThisRecipeCrafting = activeCraft?.recipeId === recipe.id;
const canCraft = recipe.ingredients.every(
(ing) => getOwnedAmount(ing.itemId) >= ing.quantity,
);
return (
<div
key={recipe.id}
className={`recipe-card ${!canCraft && !isThisRecipeCrafting ? "insufficient-resources" : ""} ${isThisRecipeCrafting ? "crafting-active" : ""}`}
onClick={() => setSelectedRecipe(recipe)}
>
<div className="recipe-icon">
{recipe.texture ? (
<img
width={64}
src={`${config.serverUrl}/static/${recipe.texture}`}
alt={recipe.displayName}
/>
) : (
<div className="fallback-icon">{recipe.displayName[0]}</div>
)}
</div>
<div className="recipe-info-main">
<span className="recipe-name">{recipe.displayName}</span>
<div className="recipe-badges">
<span className="badge-time">
<i className="fas fa-clock"></i> {recipe.time_seconds}s
</span>
</div>
</div>
{isThisRecipeCrafting && (
<div className="craft-overlay-mini">
<i className="fas fa-sync fa-spin"></i>
</div>
)}
</div>
);
})}
</div>
</div>
<CraftModal
recipe={selectedRecipe}
onClose={() => !activeCraft && setSelectedRecipe(null)}
onStartCraft={handleStartCrafting}
activeCraft={activeCraft}
getOwnedAmount={getOwnedAmount}
/>
</div>
);
};
export default CraftingTab;