Add search and bulk delete items
This commit is contained in:
+18
-2
@@ -1,4 +1,4 @@
|
||||
const state = { items: [], types: [], editingId: null };
|
||||
const state = { items: [], types: [], editingId: null, selectedIds: new Set() };
|
||||
const role = document.body.dataset.role;
|
||||
const canWrite = role === 'admin' || role === 'read_write';
|
||||
const canDelete = role === 'admin';
|
||||
@@ -21,6 +21,7 @@ async function load() {
|
||||
const query = new URLSearchParams({ q: $('#searchInput').value, type: $('#typeFilter').value, status: $('#statusFilter').value });
|
||||
const [items, summary] = await Promise.all([request(`/api/items?${query}`), request('/api/summary')]);
|
||||
state.items = items;
|
||||
state.selectedIds = new Set([...state.selectedIds].filter((itemId) => items.some((item) => item.id === itemId)));
|
||||
$('#totalCount').textContent = summary.total;
|
||||
$('#availableCount').textContent = summary.available;
|
||||
$('#checkedOutCount').textContent = summary.checked_out;
|
||||
@@ -33,7 +34,18 @@ function renderItems(items) {
|
||||
const body = $('#inventoryBody');
|
||||
const actions = (itemId) => `${canWrite ? `<button class="small-action" onclick="editItem(${itemId})">Edit</button>` : ''}${canDelete ? `<button class="small-action delete" onclick="removeItem(${itemId})">Delete</button>` : ''}`;
|
||||
$('#emptyState').style.display = items.length ? 'none' : 'block';
|
||||
body.innerHTML = items.map((item) => `<tr class="inventory-row"><td><div class="item-cell">${item.images?.length ? `<img class="item-thumb" src="${escapeHtml(item.images[0].url)}" alt="">${item.images.length > 1 ? `<span class="image-count">+${item.images.length - 1}</span>` : ''}` : '<div class="item-thumb placeholder">◌</div>'}<div><span class="item-title">${escapeHtml(item.name)}</span><span class="item-sub">${escapeHtml([item.brand, item.model].filter(Boolean).join(' · ') || 'No manufacturer details')}</span></div></div></td><td><span class="type-pill">${escapeHtml(item.item_type)}</span></td><td><span class="identifier">${escapeHtml(item.serial_number || 'No serial')}<em>${escapeHtml(item.asset_tag || 'No asset tag')}</em></span></td><td><span class="location">${escapeHtml(item.location || 'Unassigned')}</span></td><td><span class="status ${item.status.toLowerCase().replace(' ', '-')}">${escapeHtml(item.status)}</span></td><td><div class="row-actions">${actions(item.id)}</div></td></tr>`).join('');
|
||||
body.innerHTML = items.map((item) => `<tr class="inventory-row">${canDelete ? `<td class="selection-column"><input class="item-selector" type="checkbox" value="${item.id}" aria-label="Select ${escapeHtml(item.name)}"${state.selectedIds.has(item.id) ? ' checked' : ''}></td>` : ''}<td><div class="item-cell">${item.images?.length ? `<img class="item-thumb" src="${escapeHtml(item.images[0].url)}" alt="">${item.images.length > 1 ? `<span class="image-count">+${item.images.length - 1}</span>` : ''}` : '<div class="item-thumb placeholder">◌</div>'}<div><span class="item-title">${escapeHtml(item.name)}</span><span class="item-sub">${escapeHtml([item.brand, item.model].filter(Boolean).join(' · ') || 'No manufacturer details')}</span></div></div></td><td><span class="type-pill">${escapeHtml(item.item_type)}</span></td><td><span class="identifier">${escapeHtml(item.serial_number || 'No serial')}<em>${escapeHtml(item.asset_tag || 'No asset tag')}</em></span></td><td><span class="location">${escapeHtml(item.location || 'Unassigned')}</span></td><td><span class="status ${item.status.toLowerCase().replace(' ', '-')}">${escapeHtml(item.status)}</span></td><td><div class="row-actions">${actions(item.id)}</div></td></tr>`).join('');
|
||||
updateBulkActions();
|
||||
}
|
||||
|
||||
function updateBulkActions() {
|
||||
if (!canDelete) return;
|
||||
const count = state.selectedIds.size;
|
||||
$('#selectedCount').textContent = `${count} ${count === 1 ? 'item' : 'items'} selected`;
|
||||
$('#bulkActions').classList.toggle('hidden', count === 0);
|
||||
$('#bulkDeleteButton').disabled = count === 0;
|
||||
$('#selectAllItems').checked = Boolean(state.items.length) && state.items.every((item) => state.selectedIds.has(item.id));
|
||||
$('#selectAllItems').indeterminate = count > 0 && !$('#selectAllItems').checked;
|
||||
}
|
||||
|
||||
function escapeHtml(value) { return String(value).replace(/[&<>'"]/g, (char) => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[char])); }
|
||||
@@ -79,10 +91,14 @@ async function saveType(event) { event.preventDefault(); const previousName = $(
|
||||
window.editItem = (id) => openModal(state.items.find((item) => item.id === id));
|
||||
window.deleteImage = async (imageId) => { if (!state.editingId || !confirm('Delete this picture?')) return; try { const item = await request(`/api/items/${state.editingId}/images/${imageId}`, { method: 'DELETE' }); renderImageGallery(item.images); $('#imageName').textContent = `${item.images.length} saved picture${item.images.length === 1 ? '' : 's'}. Add more below.`; state.items = state.items.map((entry) => entry.id === item.id ? item : entry); renderItems(state.items); toast('Picture deleted.'); } catch (error) { toast(error.message); } };
|
||||
window.removeItem = async (id) => { const item = state.items.find((entry) => entry.id === id); if (!confirm(`Delete "${item.name}"?`)) return; try { await request(`/api/items/${id}`, { method: 'DELETE' }); toast('Item removed from inventory.'); await load(); } catch (error) { toast(error.message); } };
|
||||
async function removeSelectedItems() { const count = state.selectedIds.size; if (!count || !confirm(`Remove ${count} selected ${count === 1 ? 'item' : 'items'} from inventory?`)) return; try { const result = await request('/api/items/bulk-delete', { method: 'POST', body: JSON.stringify({ item_ids: [...state.selectedIds] }) }); state.selectedIds.clear(); toast(`${result.deleted} ${result.deleted === 1 ? 'item' : 'items'} removed from inventory.`); await load(); } catch (error) { toast(error.message); } }
|
||||
|
||||
if ($('#addButton')) $('#addButton').addEventListener('click', () => openModal());
|
||||
if ($('#emptyAddButton')) $('#emptyAddButton').addEventListener('click', () => openModal());
|
||||
if ($('#typeManagerButton')) $('#typeManagerButton').addEventListener('click', openTypeManager);
|
||||
if ($('#selectAllItems')) $('#selectAllItems').addEventListener('change', (event) => { state.items.forEach((item) => { if (event.target.checked) state.selectedIds.add(item.id); else state.selectedIds.delete(item.id); }); renderItems(state.items); });
|
||||
if ($('#inventoryBody')) $('#inventoryBody').addEventListener('change', (event) => { if (!event.target.matches('.item-selector')) return; const itemId = Number(event.target.value); if (event.target.checked) state.selectedIds.add(itemId); else state.selectedIds.delete(itemId); updateBulkActions(); });
|
||||
if ($('#bulkDeleteButton')) $('#bulkDeleteButton').addEventListener('click', removeSelectedItems);
|
||||
$('#closeModal').addEventListener('click', closeModal);
|
||||
$('#cancelButton').addEventListener('click', closeModal);
|
||||
$('#modal').addEventListener('click', (event) => { if (event.target === $('#modal')) closeModal(); });
|
||||
|
||||
Reference in New Issue
Block a user