Files
Inventory-Manager-CC/web/client/src/App.jsx

144 lines
4.9 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, { useEffect, useState } from 'react';
import InventoryGrid from './components/InventoryGrid';
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';
function App() {
const connect = useInventoryStore((state) => state.connect);
const connected = useInventoryStore((state) => state.connected);
const bridgeConnected = useInventoryStore((state) => state.bridgeConnected);
const lastUpdate = useInventoryStore((state) => state.lastUpdate);
const commandResult = useInventoryStore((state) => state.commandResult);
const alerts = useInventoryStore((state) => state.alerts) || [];
const [panelTab, setPanelTab] = useState('inventory');
const [showAlerts, setShowAlerts] = useState(false);
const [, forceRender] = useState(0);
useEffect(() => {
connect();
}, [connect]);
// Re-render every 5s so the "last updated" text stays fresh
useEffect(() => {
const id = setInterval(() => forceRender((n) => n + 1), 5000);
return () => clearInterval(id);
}, []);
const staleSecs = lastUpdate ? Math.floor((Date.now() - lastUpdate) / 1000) : null;
useEffect(() => {
connect();
}, [connect]);
const renderPanelContent = () => {
switch (panelTab) {
case 'inventory':
return <InventoryGrid />;
case 'smelting':
return <SmeltingPanel />;
case 'crafting':
return <CraftingPanel />;
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="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>
{connected && (
<div className={`connection-status ${bridgeConnected ? 'connected' : 'disconnected'}`} title="Minecraft CC:Tweaked bridge">
<span className="status-dot"></span>
{bridgeConnected ? 'Bridge OK' : 'Bridge Off'}
</div>
)}
{staleSecs !== null && staleSecs > 0 && (
<span className="last-update-text" title="Time since last data update">
{staleSecs < 60 ? `${staleSecs}s ago` : `${Math.floor(staleSecs / 60)}m ago`}
</span>
)}
</div>
</div>
{commandResult && (
<div className={`command-toast ${commandResult.success ? 'success' : 'error'}`}>
{commandResult.message || commandResult.error || (commandResult.success ? 'OK' : 'Failed')}
</div>
)}
{/* Alerts popup overlay */}
<AlertsPanel isOpen={showAlerts} onClose={() => setShowAlerts(false)} />
<div className="app-content">
<div className="sidebar">
<ErrorBoundary>
<StorageOverview />
</ErrorBoundary>
</div>
<div className="main-panel">
<div className="panel-tabs">
<button
className={panelTab === 'inventory' ? 'active' : ''}
onClick={() => setPanelTab('inventory')}
>
📦 Inventory
</button>
<button
className={panelTab === 'smelting' ? 'active' : ''}
onClick={() => setPanelTab('smelting')}
>
🔥 Smelting
</button>
<button
className={panelTab === 'crafting' ? 'active' : ''}
onClick={() => setPanelTab('crafting')}
>
🔨 Crafting
</button>
<button
className={panelTab === 'analytics' ? 'active' : ''}
onClick={() => setPanelTab('analytics')}
>
📊 Analytics
</button>
</div>
<div className="panel-content-wrapper">
<ErrorBoundary>
{renderPanelContent()}
</ErrorBoundary>
</div>
</div>
</div>
</div>
);
}
export default App;