Files
Inventory-Manager-CC/web/client/src/components/AlertsPanel.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

93 lines
3.4 KiB
JavaScript

import React, { useEffect, useRef } from 'react';
import { useInventoryStore } from '../store/inventoryStore';
import { formatItemName } from '../utils/itemUtils';
import ItemIcon from './ItemIcon';
import './AlertsPanel.css';
function AlertsPanel({ isOpen, onClose }) {
const alerts = useInventoryStore((state) => state.alerts) || [];
const panelRef = useRef(null);
// Close on click outside
useEffect(() => {
if (!isOpen) return;
const handleClickOutside = (e) => {
if (panelRef.current && !panelRef.current.contains(e.target)) {
// Check if the click was on the bell button (has alerts-bell class)
if (e.target.closest('.alerts-bell')) return;
onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [isOpen, onClose]);
// Close on Escape
useEffect(() => {
if (!isOpen) return;
const handleEsc = (e) => { if (e.key === 'Escape') onClose(); };
document.addEventListener('keydown', handleEsc);
return () => document.removeEventListener('keydown', handleEsc);
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div className="alerts-overlay" ref={panelRef}>
<div className="alerts-popup">
<div className="alerts-popup-header">
<h3>🔔 Low-Stock Alerts</h3>
<span className="alert-count">
{alerts.length} alert{alerts.length !== 1 ? 's' : ''}
</span>
<button className="alerts-close" onClick={onClose}></button>
</div>
<div className="alerts-popup-body">
{alerts.length > 0 ? (
<div className="alert-list">
{alerts.map((alert, idx) => {
const itemName = alert.item || alert.name || alert.label || '';
const isTriggered = alert.triggered !== undefined ? alert.triggered : true;
const current = alert.current ?? '?';
const threshold = alert.threshold || alert.min || '?';
return (
<div key={idx} className={`alert-card ${isTriggered ? 'triggered' : 'ok'}`}>
<div className="alert-icon">
{isTriggered ? '🔴' : '🟢'}
</div>
<div className="alert-info">
<div className="alert-item-row">
<ItemIcon itemName={itemName} size={20} />
<span className="alert-item-name">{formatItemName(itemName)}</span>
</div>
<div className="alert-details">
<span className="alert-stock">
Stock: <strong>{current}</strong>
</span>
<span className="alert-threshold">
Min: <strong>{threshold}</strong>
</span>
</div>
</div>
<div className={`alert-badge ${isTriggered ? 'low' : 'ok'}`}>
{isTriggered ? 'LOW' : 'OK'}
</div>
</div>
);
})}
</div>
) : (
<div className="no-alerts">
<span className="no-alerts-icon"></span>
<span>All stock levels are OK</span>
</div>
)}
</div>
</div>
</div>
);
}
export default AlertsPanel;