Add InventoryGrid component for displaying and managing inventory items
This commit is contained in:
155
web/client/src/components/InventoryGrid.jsx
Normal file
155
web/client/src/components/InventoryGrid.jsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useInventoryStore } from '../store/inventoryStore';
|
||||
import { formatItemName, getItemEmoji, formatCount } from '../utils/itemUtils';
|
||||
import ItemIcon from './ItemIcon';
|
||||
import './InventoryGrid.css';
|
||||
|
||||
function InventoryGrid() {
|
||||
const inventory = useInventoryStore((state) => state.inventory);
|
||||
const searchQuery = useInventoryStore((state) => state.searchQuery);
|
||||
const setSearchQuery = useInventoryStore((state) => state.setSearchQuery);
|
||||
const getFilteredItems = useInventoryStore((state) => state.getFilteredItems);
|
||||
const orderItem = useInventoryStore((state) => state.orderItem);
|
||||
|
||||
const [selectedItem, setSelectedItem] = useState(null);
|
||||
const [orderAmount, setOrderAmount] = useState(1);
|
||||
const [sortBy, setSortBy] = useState('count'); // 'count', 'name'
|
||||
|
||||
const items = getFilteredItems();
|
||||
|
||||
const sortedItems = [...items].sort((a, b) => {
|
||||
if (sortBy === 'count') return (b.count || 0) - (a.count || 0);
|
||||
if (sortBy === 'name') return (a.displayName || a.name || '').localeCompare(b.displayName || b.name || '');
|
||||
return 0;
|
||||
});
|
||||
|
||||
const handleOrder = useCallback(async () => {
|
||||
if (!selectedItem || orderAmount <= 0) return;
|
||||
await orderItem(selectedItem.name, orderAmount);
|
||||
}, [selectedItem, orderAmount, orderItem]);
|
||||
|
||||
const handleItemClick = (item) => {
|
||||
setSelectedItem(item);
|
||||
setOrderAmount(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="inventory-panel">
|
||||
{/* Search & controls bar */}
|
||||
<div className="inventory-toolbar">
|
||||
<div className="search-box">
|
||||
<span className="search-icon">🔍</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search items..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="search-input"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button className="search-clear" onClick={() => setSearchQuery('')}>✕</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="sort-controls">
|
||||
<button
|
||||
className={`mc-btn ${sortBy === 'count' ? 'green' : ''}`}
|
||||
onClick={() => setSortBy('count')}
|
||||
>
|
||||
# Count
|
||||
</button>
|
||||
<button
|
||||
className={`mc-btn ${sortBy === 'name' ? 'green' : ''}`}
|
||||
onClick={() => setSortBy('name')}
|
||||
>
|
||||
A Name
|
||||
</button>
|
||||
</div>
|
||||
<div className="item-count-label">
|
||||
{sortedItems.length} types · {(inventory.grandTotal || 0).toLocaleString()} items
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="inventory-body">
|
||||
{/* Item grid */}
|
||||
<div className="item-grid-wrapper">
|
||||
<div className="item-grid">
|
||||
{sortedItems.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className={`item-slot ${selectedItem?.name === item.name ? 'selected' : ''}`}
|
||||
onClick={() => handleItemClick(item)}
|
||||
title={`${formatItemName(item.name)}\n${item.count} total`}
|
||||
>
|
||||
<ItemIcon itemName={item.name} size={32} />
|
||||
<span className="item-slot-count">{formatCount(item.count)}</span>
|
||||
<span className="item-slot-name">{formatItemName(item.name)}</span>
|
||||
</div>
|
||||
))}
|
||||
{sortedItems.length === 0 && (
|
||||
<div className="empty-grid">
|
||||
{searchQuery ? `No items matching "${searchQuery}"` : 'No items in storage'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Item detail / order panel */}
|
||||
{selectedItem && (
|
||||
<div className="item-detail-panel">
|
||||
<div className="detail-header">
|
||||
<ItemIcon itemName={selectedItem.name} size={48} />
|
||||
<div className="detail-info">
|
||||
<h3>{formatItemName(selectedItem.name)}</h3>
|
||||
<span className="detail-id">{selectedItem.name}</span>
|
||||
</div>
|
||||
<button className="detail-close" onClick={() => setSelectedItem(null)}>✕</button>
|
||||
</div>
|
||||
|
||||
<div className="detail-stats">
|
||||
<div className="detail-stat">
|
||||
<span className="label">Total</span>
|
||||
<span className="value">{(selectedItem.count || 0).toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="detail-stat">
|
||||
<span className="label">Stacks</span>
|
||||
<span className="value">
|
||||
{Math.ceil((selectedItem.count || 0) / 64)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="order-section">
|
||||
<h4>📤 Order Items</h4>
|
||||
<div className="order-controls">
|
||||
<div className="order-amount-controls">
|
||||
<button className="mc-btn" onClick={() => setOrderAmount(Math.max(1, orderAmount - 1))}>-</button>
|
||||
<input
|
||||
type="number"
|
||||
className="order-input"
|
||||
value={orderAmount}
|
||||
onChange={(e) => setOrderAmount(Math.max(1, parseInt(e.target.value) || 1))}
|
||||
min="1"
|
||||
max={selectedItem.count || 1}
|
||||
/>
|
||||
<button className="mc-btn" onClick={() => setOrderAmount(Math.min(selectedItem.count || 1, orderAmount + 1))}>+</button>
|
||||
</div>
|
||||
<div className="order-presets">
|
||||
<button className="mc-btn" onClick={() => setOrderAmount(1)}>1</button>
|
||||
<button className="mc-btn" onClick={() => setOrderAmount(16)}>16</button>
|
||||
<button className="mc-btn" onClick={() => setOrderAmount(32)}>32</button>
|
||||
<button className="mc-btn" onClick={() => setOrderAmount(64)}>64</button>
|
||||
<button className="mc-btn" onClick={() => setOrderAmount(selectedItem.count || 1)}>All</button>
|
||||
</div>
|
||||
<button className="mc-btn green order-btn" onClick={handleOrder}>
|
||||
📤 Dispense x{orderAmount}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default InventoryGrid;
|
||||
Reference in New Issue
Block a user