Files
Inventory-Manager-CC/web/client/src/components/SmeltingPanel.jsx
MayaTheShy 29498a2f6a Major UI overhaul: bigger icons, creative inventory tabs, grouped smelting, alerts popup, analytics, mod icons
- Inventory: Bigger item icons (48px) filling their slots, MC-style hover tooltips
- Creative inventory category tabs (All/Blocks/Tools/Combat/Food/Redstone/Materials/Misc)
- Smelting recipes grouped by output item with collapsible sections
- Alerts moved from full tab to popup overlay triggered from header bell icon
- New Analytics tab with SVG charts (storage over time, top items, item trend lookup)
- Mod icon support for ComputerCraft (CC:Tweaked) via GitHub CDN fallback
- Server: new /api/history-summary endpoint for aggregate storage history
- Store: fetchHistorySummary and fetchItemHistory for analytics data
2026-03-21 19:07:17 -04:00

185 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useMemo } from 'react';
import { useInventoryStore } from '../store/inventoryStore';
import { formatItemName } from '../utils/itemUtils';
import ItemIcon from './ItemIcon';
import './SmeltingPanel.css';
function SmeltingPanel() {
const inventory = useInventoryStore((state) => state.inventory);
const smeltingPaused = useInventoryStore((state) => state.smeltingPaused);
const disabledRecipes = useInventoryStore((state) => state.disabledRecipes);
const smeltable = useInventoryStore((state) => state.smeltable);
const toggleSmelting = useInventoryStore((state) => state.toggleSmelting);
const toggleRecipe = useInventoryStore((state) => state.toggleRecipe);
const enableAllRecipes = useInventoryStore((state) => state.enableAllRecipes);
const disableAllRecipes = useInventoryStore((state) => state.disableAllRecipes);
const [collapsedGroups, setCollapsedGroups] = useState({});
const furnaceStatus = inventory.furnaceStatus || {};
const furnaceEntries = Object.entries(furnaceStatus);
const smeltableEntries = Object.entries(smeltable || {});
// Group recipes by output item
const groupedRecipes = useMemo(() => {
const groups = {};
for (const [input, recipe] of smeltableEntries) {
const result = (recipe && recipe.result) || 'unknown';
if (!groups[result]) {
groups[result] = [];
}
groups[result].push({ input, recipe });
}
// Sort groups by output name
return Object.entries(groups).sort(([a], [b]) =>
formatItemName(a).localeCompare(formatItemName(b))
);
}, [smeltableEntries]);
const toggleGroup = (groupKey) => {
setCollapsedGroups((prev) => ({
...prev,
[groupKey]: !prev[groupKey],
}));
};
return (
<div className="smelting-panel">
{/* Header */}
<div className="smelting-header">
<h2>🔥 Smelting Dashboard</h2>
<div className="smelting-controls">
<button
className={`mc-btn ${smeltingPaused ? 'green' : 'red'}`}
onClick={toggleSmelting}
>
{smeltingPaused ? '▶ Resume' : '⏸ Pause'}
</button>
</div>
</div>
{smeltingPaused && (
<div className="smelting-paused-banner"> Smelting is PAUSED</div>
)}
{/* Furnace status */}
<div className="furnace-section detail-section">
<h3>🔥 Furnaces ({inventory.furnaceCount || 0})</h3>
{furnaceEntries.length > 0 ? (
<div className="furnace-grid">
{furnaceEntries.map(([name, status]) => (
<div key={name} className={`furnace-card ${(status && status.active) ? 'active' : 'idle'}`}>
<div className="furnace-name">{(name || '').replace(/^minecraft:/, '')}</div>
<div className="furnace-status">
{status && status.input && (
<div className="furnace-slot">
<span className="slot-label">In:</span>
<ItemIcon itemName={status.input.name} size={16} />
<span>{status.input.count || 0}</span>
</div>
)}
{status && status.fuel && (
<div className="furnace-slot">
<span className="slot-label">Fuel:</span>
<ItemIcon itemName={status.fuel.name} size={16} />
<span>{status.fuel.count || 0}</span>
</div>
)}
{status && status.output && (
<div className="furnace-slot">
<span className="slot-label">Out:</span>
<ItemIcon itemName={status.output.name} size={16} />
<span>{status.output.count || 0}</span>
</div>
)}
{(!status || (!status.input && !status.fuel && !status.output)) && (
<span className="furnace-empty">Empty</span>
)}
</div>
</div>
))}
</div>
) : (
<div className="no-data">No furnace data available</div>
)}
</div>
{/* Recipe list - Grouped by output */}
<div className="recipe-section detail-section">
<h3>📋 Smelting Recipes ({smeltableEntries.length})</h3>
<div className="recipe-bulk-controls">
<button className="mc-btn green" onClick={enableAllRecipes}> Enable All</button>
<button className="mc-btn red" onClick={disableAllRecipes}> Disable All</button>
</div>
<div className="recipe-groups">
{groupedRecipes.map(([outputName, recipes]) => {
const isCollapsed = collapsedGroups[outputName];
const enabledCount = recipes.filter((r) => !disabledRecipes[r.input]).length;
return (
<div key={outputName} className="recipe-group">
<div
className="recipe-group-header"
onClick={() => toggleGroup(outputName)}
>
<span className="recipe-group-toggle">
{isCollapsed ? '▶' : '▼'}
</span>
<ItemIcon itemName={outputName} size={24} />
<span className="recipe-group-name">
{formatItemName(outputName)}
</span>
<span className="recipe-group-count">
{enabledCount}/{recipes.length} enabled
</span>
</div>
{!isCollapsed && (
<div className="recipe-group-items">
{recipes.map(({ input, recipe }) => {
const isDisabled = disabledRecipes[input];
const furnaces = (recipe && recipe.furnaces) || [];
return (
<div
key={input}
className={`recipe-item ${isDisabled ? 'disabled' : 'enabled'}`}
onClick={() => toggleRecipe(input)}
>
<div className="recipe-toggle">
{isDisabled ? '⬜' : '✅'}
</div>
<div className="recipe-input">
<ItemIcon itemName={input} size={20} />
<span>{formatItemName(input)}</span>
</div>
<span className="recipe-arrow"></span>
<div className="recipe-output">
<ItemIcon itemName={outputName} size={20} />
</div>
<div className="recipe-furnaces">
{furnaces.map((f) => (
<span key={f} className="furnace-tag">
{(f || '').replace(/^minecraft:/, '')}
</span>
))}
</div>
</div>
);
})}
</div>
)}
</div>
);
})}
{groupedRecipes.length === 0 && (
<div className="no-data">No smelting recipes loaded</div>
)}
</div>
</div>
</div>
);
}
export default SmeltingPanel;