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

165 lines
5.7 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 SettingsPanel from './components/SettingsPanel';
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 [showSettings, setShowSettings] = useState(false);
const [, forceRender] = useState(0);
const turtleDashboardUrl = import.meta.env.VITE_TURTLE_DASHBOARD_URL || 'https://turtles.spatulaa.com';
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;
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">
{/* Cross-link to Turtle Dashboard */}
<a
href={turtleDashboardUrl}
className="cross-link-btn"
title="Open Turtle Control Dashboard"
target="_blank"
rel="noopener noreferrer"
>
🐢 Turtles
</a>
{/* Settings gear button */}
<button
className="settings-gear"
onClick={() => setShowSettings(!showSettings)}
title="Settings"
>
</button>
{/* 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)} />
{/* Settings overlay */}
<SettingsPanel isOpen={showSettings} onClose={() => setShowSettings(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;