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
This commit is contained in:
MayaTheShy
2026-03-21 19:07:17 -04:00
parent c25ef9f2cc
commit 29498a2f6a
15 changed files with 1244 additions and 150 deletions

View File

@@ -4,6 +4,7 @@ import StorageOverview from './components/StorageOverview';
import SmeltingPanel from './components/SmeltingPanel';
import CraftingPanel from './components/CraftingPanel';
import AlertsPanel from './components/AlertsPanel';
import AnalyticsPanel from './components/AnalyticsPanel';
import ErrorBoundary from './components/ErrorBoundary';
import { useInventoryStore } from './store/inventoryStore';
import './App.css';
@@ -12,7 +13,9 @@ function App() {
const connect = useInventoryStore((state) => state.connect);
const connected = useInventoryStore((state) => state.connected);
const commandResult = useInventoryStore((state) => state.commandResult);
const alerts = useInventoryStore((state) => state.alerts) || [];
const [panelTab, setPanelTab] = useState('inventory');
const [showAlerts, setShowAlerts] = useState(false);
useEffect(() => {
connect();
@@ -26,20 +29,35 @@ function App() {
return <SmeltingPanel />;
case 'crafting':
return <CraftingPanel />;
case 'alerts':
return <AlertsPanel />;
case 'analytics':
return <AnalyticsPanel />;
default:
return <InventoryGrid />;
}
};
const triggeredAlerts = alerts.filter((a) => a.triggered !== false);
return (
<div className="app">
<div className="app-header">
<h1> Inventory Manager</h1>
<div className={`connection-status ${connected ? 'connected' : 'disconnected'}`}>
<span className="status-dot"></span>
{connected ? 'Connected' : 'Disconnected'}
<div className="header-right">
{/* Alerts bell button */}
<button
className={`alerts-bell ${triggeredAlerts.length > 0 ? 'has-alerts' : ''}`}
onClick={() => setShowAlerts(!showAlerts)}
title="Low-stock alerts"
>
🔔
{triggeredAlerts.length > 0 && (
<span className="alerts-bell-badge">{triggeredAlerts.length}</span>
)}
</button>
<div className={`connection-status ${connected ? 'connected' : 'disconnected'}`}>
<span className="status-dot"></span>
{connected ? 'Connected' : 'Disconnected'}
</div>
</div>
</div>
@@ -49,6 +67,9 @@ function App() {
</div>
)}
{/* Alerts popup overlay */}
<AlertsPanel isOpen={showAlerts} onClose={() => setShowAlerts(false)} />
<div className="app-content">
<div className="sidebar">
<ErrorBoundary>
@@ -76,10 +97,10 @@ function App() {
🔨 Crafting
</button>
<button
className={panelTab === 'alerts' ? 'active' : ''}
onClick={() => setPanelTab('alerts')}
className={panelTab === 'analytics' ? 'active' : ''}
onClick={() => setPanelTab('analytics')}
>
🔔 Alerts
📊 Analytics
</button>
</div>
<div className="panel-content-wrapper">