184 lines
5.4 KiB
JavaScript
184 lines
5.4 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";
|
|
|
|
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);
|
|
console.log(filteredRecipes);
|
|
setRecipes(filteredRecipes);
|
|
}
|
|
}, [activeCategory]);
|
|
|
|
useEffect(() => {
|
|
if (!socket) return;
|
|
|
|
socket.emit("player:get_inventory");
|
|
|
|
const handleInventory = (data) => setUserInventory(data);
|
|
socket.on("player:inventory_data", handleInventory);
|
|
|
|
return () => {
|
|
socket.off("player:inventory_data", handleInventory);
|
|
};
|
|
}, [socket]);
|
|
|
|
useEffect(() => {
|
|
let timer;
|
|
if (activeCraft && activeCraft.timeLeft > 0) {
|
|
timer = setInterval(() => {
|
|
setActiveCraft((prev) => ({
|
|
...prev,
|
|
timeLeft: Math.max(0, prev.timeLeft - 1),
|
|
}));
|
|
}, 1000);
|
|
} else if (activeCraft && activeCraft.timeLeft === 0) {
|
|
setActiveCraft(null);
|
|
socket.emit("player:get_inventory");
|
|
}
|
|
return () => clearInterval(timer);
|
|
}, [activeCraft, socket]);
|
|
|
|
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,
|
|
category: activeCategory,
|
|
});
|
|
|
|
setActiveCraft({
|
|
name: recipe.displayName,
|
|
timeLeft: recipe.constructionTime,
|
|
totalTime: recipe.constructionTime,
|
|
});
|
|
|
|
setSelectedRecipe(null);
|
|
};
|
|
|
|
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: `${((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 canCraft = recipe.ingredients.every(
|
|
(ing) => getOwnedAmount(ing.itemId) >= ing.quantity,
|
|
);
|
|
|
|
return (
|
|
<div
|
|
key={recipe.id}
|
|
className={`recipe-card ${!canCraft ? "insufficient-resources" : ""}`}
|
|
onClick={() => setSelectedRecipe(recipe)}
|
|
>
|
|
<div className="recipe-icon">
|
|
{recipe.texture ? (
|
|
<img src={recipe.texture} alt={recipe.displayName} />
|
|
) : (
|
|
<div className="fallback-icon">
|
|
{recipe.id[0].toUpperCase()}
|
|
</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.constructionTime}
|
|
s
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{!canCraft && (
|
|
<div className="lock-overlay">
|
|
<i className="fas fa-lock"></i>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{recipes.length === 0 && (
|
|
<div className="empty-category">
|
|
No blueprints available in this sector.
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<CraftModal
|
|
recipe={selectedRecipe}
|
|
onClose={() => setSelectedRecipe(null)}
|
|
onStartCraft={handleStartCrafting}
|
|
isBusy={!!activeCraft}
|
|
getOwnedAmount={getOwnedAmount}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CraftingTab;
|