164 lines
5.0 KiB
JavaScript
164 lines
5.0 KiB
JavaScript
import React from "react";
|
|
import "./CraftModal.css";
|
|
import { getServerUrl } from "../../../../config/api";
|
|
|
|
const CraftModal = ({
|
|
recipe,
|
|
onClose,
|
|
onStartCraft,
|
|
activeCraft,
|
|
getOwnedAmount,
|
|
}) => {
|
|
if (!recipe) return null;
|
|
|
|
const CONNECT_URL = getServerUrl();
|
|
const ASSET_BASE_URL = `${CONNECT_URL}/static/`;
|
|
|
|
const getFullTextureUrl = (path) => {
|
|
if (!path) return "/assets/no-image.png";
|
|
return path.startsWith("http") ? path : `${ASSET_BASE_URL}${path}`;
|
|
};
|
|
|
|
const isBusy = !!activeCraft;
|
|
const outputQty = Object.values(recipe.output || {})[0] || 1;
|
|
const canAfford = recipe.ingredients?.every(
|
|
(ing) => getOwnedAmount(ing.itemId) >= ing.quantity,
|
|
);
|
|
|
|
return (
|
|
<div className="craft-modal-overlay" onClick={onClose}>
|
|
<div className="craft-modal" onClick={(e) => e.stopPropagation()}>
|
|
<button className="close-btn-top" onClick={onClose}>
|
|
×
|
|
</button>
|
|
|
|
{/* Header: Icon + Title */}
|
|
<div className="modal-header-compact">
|
|
<div className="item-icon-box">
|
|
<img
|
|
src={getFullTextureUrl(recipe.texture)}
|
|
alt={recipe.displayName}
|
|
/>
|
|
<div
|
|
className="item-qty-badge"
|
|
style={{
|
|
position: "absolute",
|
|
bottom: "-5px",
|
|
right: "-5px",
|
|
background: "#00d2ff",
|
|
color: "#000",
|
|
fontSize: "10px",
|
|
padding: "2px 5px",
|
|
borderRadius: "3px",
|
|
fontWeight: "bold",
|
|
}}
|
|
>
|
|
x{outputQty}
|
|
</div>
|
|
</div>
|
|
<div className="item-info-title">
|
|
<span className="item-tag">PROTOTYPE_UNIT</span>
|
|
<h3>{recipe.displayName}</h3>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Description */}
|
|
<div className="details-section">
|
|
<p className="description-text">
|
|
{recipe.description ||
|
|
"Advanced composite material for high-tier construction."}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Resources */}
|
|
<div className="details-section">
|
|
<span className="section-label">Required Materials</span>
|
|
<div className="res-container">
|
|
{recipe.ingredients?.map((ing) => {
|
|
const owned = getOwnedAmount(ing.itemId);
|
|
const hasEnough = owned >= ing.quantity;
|
|
return (
|
|
<div key={ing.itemId} className="res-row">
|
|
<span className="res-name">
|
|
<i
|
|
className={`fas fa-square`}
|
|
style={{
|
|
fontSize: "8px",
|
|
color: hasEnough ? "#00ff88" : "#ff4444",
|
|
}}
|
|
></i>
|
|
{ing.displayName}
|
|
</span>
|
|
<span
|
|
className={`res-amount ${hasEnough ? "val-good" : "val-bad"}`}
|
|
>
|
|
{owned} / {ing.quantity}
|
|
</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Outcome Info */}
|
|
<div className="outcome-bar">
|
|
<span>
|
|
Production Time: <strong>{recipe.time_seconds}s</strong>
|
|
</span>
|
|
</div>
|
|
|
|
{/* Action Button */}
|
|
<div className="modal-footer-minimal">
|
|
{activeCraft && activeCraft.recipeId === recipe.id ? (
|
|
<div style={{ marginTop: "15px" }}>
|
|
<div
|
|
style={{
|
|
fontSize: "12px",
|
|
color: "#00d2ff",
|
|
marginBottom: "5px",
|
|
textAlign: "center",
|
|
}}
|
|
>
|
|
Constructing... {activeCraft.timeLeft}s
|
|
</div>
|
|
<div
|
|
className="progress-bar-bg"
|
|
style={{
|
|
height: "4px",
|
|
background: "rgba(255,255,255,0.1)",
|
|
borderRadius: "2px",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<div
|
|
className="progress-bar-fill"
|
|
style={{
|
|
width: `${((activeCraft.totalTime - activeCraft.timeLeft) / activeCraft.totalTime) * 100}%`,
|
|
height: "100%",
|
|
background: "#00d2ff",
|
|
boxShadow: "0 0 10px #00d2ff",
|
|
}}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<button
|
|
className="btn-craft-action btn-primary-craft"
|
|
disabled={!canAfford || isBusy}
|
|
onClick={() => canAfford && !isBusy && onStartCraft(recipe)}
|
|
>
|
|
{isBusy
|
|
? "System Busy"
|
|
: !canAfford
|
|
? "Low Resources"
|
|
: "Begin Construction"}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CraftModal;
|