@@ -821,11 +835,15 @@
let containerTypes = {};
let containerCategories = [];
+ // Item categories - loaded from categories.json
+ let itemCategories = {};
+
let inventory = [];
let isUnlocked = false;
let currentPin = null;
let currentFilter = 'all';
let containerFilter = null;
+ let categoryFilter = null;
let hasPinSet = false;
// API calls
@@ -909,10 +927,73 @@
});
}
+ // Load item categories from JSON file
+ async function loadCategories() {
+ try {
+ const response = await fetch('categories.json');
+ itemCategories = await response.json();
+ buildCategoryDropdown();
+ buildCategoryTabs();
+ } catch (error) {
+ console.error('Error loading categories:', error);
+ }
+ }
+
+ // Build the category dropdown
+ function buildCategoryDropdown() {
+ const select = document.getElementById('itemCategory');
+ select.replaceChildren();
+ Object.entries(itemCategories).forEach(([key, cat]) => {
+ const option = document.createElement('option');
+ option.value = key;
+ option.textContent = cat.name;
+ select.appendChild(option);
+ });
+ }
+
+ // Build category filter tabs
+ function buildCategoryTabs() {
+ const navTabs = document.getElementById('navTabs');
+ // Remove old category tabs if they exist
+ navTabs.querySelectorAll('.nav-tab[data-category]').forEach(tab => tab.remove());
+
+ // Add category tabs after "Out of Stock" tab, sorted alphabetically
+ const outTab = navTabs.querySelector('[data-filter="out"]');
+ const sortedCategories = Object.entries(itemCategories)
+ .sort((a, b) => a[1].name.localeCompare(b[1].name))
+ .reverse(); // Reverse so insertAdjacentElement builds correct order
+ sortedCategories.forEach(([key, cat]) => {
+ const tab = document.createElement('button');
+ tab.className = 'nav-tab';
+ tab.dataset.category = key;
+ tab.textContent = cat.name;
+ tab.style.borderLeft = `3px solid ${cat.color}`;
+ tab.setAttribute('role', 'tab');
+ tab.setAttribute('aria-selected', 'false');
+ tab.addEventListener('click', () => {
+ // Clear other active states and aria-selected
+ navTabs.querySelectorAll('.nav-tab').forEach(t => {
+ t.classList.remove('active');
+ t.setAttribute('aria-selected', 'false');
+ });
+ tab.classList.add('active');
+ tab.setAttribute('aria-selected', 'true');
+ currentFilter = 'category';
+ categoryFilter = key;
+ document.getElementById('inventoryList').style.display = 'block';
+ document.getElementById('containerLegend').style.display = 'grid';
+ document.getElementById('qrSection').style.display = 'none';
+ renderInventory();
+ });
+ outTab.insertAdjacentElement('afterend', tab);
+ });
+ }
+
// Initialize
async function init() {
try {
await loadContainers();
+ await loadCategories();
const result = await apiCall('get');
inventory = result.inventory || [];
@@ -987,14 +1068,14 @@
function renderInventory() {
const list = document.getElementById('inventoryList');
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
-
+
let filtered = inventory.filter(item => {
const matchesSearch = item.name.toLowerCase().includes(searchTerm);
const matchesFilter = currentFilter === 'all' ||
currentFilter === 'recent' ||
(currentFilter === 'in-stock' && !item.outOfStock) ||
(currentFilter === 'out' && item.outOfStock) ||
- (currentFilter === 'spices' && item.container.startsWith('spice-'));
+ (currentFilter === 'category' && item.category === categoryFilter);
const matchesContainer = !containerFilter || item.container === containerFilter;
return matchesSearch && matchesFilter && matchesContainer;
});
@@ -1024,20 +1105,23 @@
return;
}
- list.innerHTML = filtered.map(item => `
-
+ list.innerHTML = filtered.map(item => {
+ const cat = itemCategories[item.category];
+ return `
+
${escapeHtml(item.name)}
${containerTypes[item.container] ? `${containerTypes[item.container].category}: ${containerTypes[item.container].name}` : 'Unknown'}
+ ${cat ? `
${cat.name}` : ''}
${item.outOfStock ? 'Out' : 'In Stock'}
- `).join('');
+ `}).join('');
}
function escapeHtml(text) {
@@ -1113,14 +1197,19 @@
function setupEventListeners() {
document.getElementById('searchInput').addEventListener('input', renderInventory);
- document.querySelectorAll('.nav-tab').forEach(tab => {
+ document.querySelectorAll('.nav-tab[data-filter]').forEach(tab => {
tab.addEventListener('click', () => {
- document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
+ document.querySelectorAll('.nav-tab').forEach(t => {
+ t.classList.remove('active');
+ t.setAttribute('aria-selected', 'false');
+ });
tab.classList.add('active');
-
+ tab.setAttribute('aria-selected', 'true');
+
const filter = tab.dataset.filter;
currentFilter = filter;
-
+ categoryFilter = null; // Clear category filter when switching to non-category tabs
+
if (filter === 'qr') {
document.getElementById('inventoryList').style.display = 'none';
document.getElementById('containerLegend').style.display = 'none';
@@ -1342,10 +1431,11 @@
toggleLock();
return;
}
-
+
document.getElementById('itemModalTitle').textContent = 'Add Item';
document.getElementById('itemName').value = '';
document.getElementById('itemContainer').selectedIndex = 0;
+ document.getElementById('itemCategory').value = 'other';
document.getElementById('itemOutOfStock').checked = false;
document.getElementById('editingItemId').value = '';
document.getElementById('deleteBtn').style.display = 'none';
@@ -1357,13 +1447,14 @@
if (!isUnlocked) {
return; // View only mode
}
-
+
const item = inventory.find(i => i.id === id);
if (!item) return;
-
+
document.getElementById('itemModalTitle').textContent = 'Edit Item';
document.getElementById('itemName').value = item.name;
document.getElementById('itemContainer').value = item.container;
+ document.getElementById('itemCategory').value = item.category || 'other';
document.getElementById('itemOutOfStock').checked = item.outOfStock;
document.getElementById('editingItemId').value = id;
document.getElementById('deleteBtn').style.display = 'block';
@@ -1373,9 +1464,10 @@
async function saveItem() {
const name = document.getElementById('itemName').value.trim();
const container = document.getElementById('itemContainer').value;
+ const category = document.getElementById('itemCategory').value;
const outOfStock = document.getElementById('itemOutOfStock').checked;
const editingId = document.getElementById('editingItemId').value;
-
+
if (!name) {
showToast('Please enter an item name');
return;
@@ -1401,6 +1493,7 @@
id: parseInt(editingId),
name,
container,
+ category,
outOfStock,
pin: currentPin
});
@@ -1409,13 +1502,14 @@
if (item) {
item.name = name;
item.container = container;
+ item.category = category;
item.outOfStock = outOfStock;
}
showToast('Item updated');
} else {
// Add new
- const result = await apiCall('add', { name, container, outOfStock, pin: currentPin });
-
+ const result = await apiCall('add', { name, container, category, outOfStock, pin: currentPin });
+
if (result.success && result.item) {
inventory.push(result.item);
}