diff --git a/admin/menu.js b/admin/menu.js index e04220b..3e9047e 100644 --- a/admin/menu.js +++ b/admin/menu.js @@ -147,7 +147,7 @@ document.getElementById('categories-container').addEventListener('click', async // Edit Item if (target.classList.contains('btn-edit-item')) { - const row = target.closest('tr'); + const row = target.closest('.item-row'); openItemModal(JSON.parse(row.dataset.item)); return; } @@ -165,8 +165,8 @@ document.getElementById('categories-container').addEventListener('click', async // Move Item Up/Down if (target.classList.contains('btn-move-item')) { const direction = target.dataset.direction; - const row = target.closest('tr'); - const tbody = row.parentElement; + const row = target.closest('.item-row'); + const rowsContainer = row.parentElement; const sibling = direction === 'up' ? row.previousElementSibling : row.nextElementSibling; if (!sibling) return; // already at this edge; button should be disabled anyway @@ -185,14 +185,14 @@ document.getElementById('categories-container').addEventListener('click', async return; } if (direction === 'up') { - tbody.insertBefore(row, sibling); + rowsContainer.insertBefore(row, sibling); } else { - tbody.insertBefore(sibling, row); + rowsContainer.insertBefore(sibling, row); } // Re-derives disabled state from DOM position rather than re-enabling // `target` unconditionally, since the swap may have just moved it // onto an edge (e.g. into the first/last slot) where it should stay disabled. - refreshMoveButtonStates(tbody); + refreshMoveButtonStates(rowsContainer); } catch { alert('Network error. Please try again.'); target.disabled = false; @@ -235,10 +235,10 @@ document.getElementById('categories-container').addEventListener('click', async return; } - // Toggle Availability (Mark Sold Out / Mark Available) + // Toggle Availability — the status badge itself is the button now, so + // `target` IS the badge; no separate "Mark Sold Out" control to sync. if (target.classList.contains('btn-toggle-item')) { - const row = target.closest('tr'); - const statusBadge = row.querySelector('.status-badge'); + const row = target.closest('.item-row'); target.disabled = true; const fd = new FormData(); @@ -249,9 +249,9 @@ document.getElementById('categories-container').addEventListener('click', async try { const data = await postForm(fd); if (data.success) { - statusBadge.className = `status-badge ${data.status_class}`; - statusBadge.textContent = data.status_badge; - target.textContent = data.btn_text; + target.className = `status-badge btn-toggle-item ${data.status_class}`; + target.textContent = data.status_badge; + target.title = `Click to mark as ${data.is_available ? 'sold out' : 'available'}`; const currentData = JSON.parse(row.dataset.item); currentData.is_available = data.is_available ? 1 : 0; @@ -268,8 +268,8 @@ document.getElementById('categories-container').addEventListener('click', async // Delete Item if (target.classList.contains('btn-delete-item')) { if (!confirm('Are you sure you want to delete this menu item?')) return; - const row = target.closest('tr'); - const tbody = row?.parentElement; + const row = target.closest('.item-row'); + const rowsContainer = row?.parentElement; const fd = new FormData(); fd.append('action', 'delete_item'); @@ -284,7 +284,7 @@ document.getElementById('categories-container').addEventListener('click', async row.style.transform = 'translateX(-10px)'; setTimeout(() => { row.remove(); - refreshMoveButtonStates(tbody); + refreshMoveButtonStates(rowsContainer); }, 200); } } catch { @@ -323,10 +323,10 @@ document.getElementById('categories-container').addEventListener('click', async // Sets the up/down move buttons' disabled state from each row's actual DOM // position, rather than tracking indices by hand — first row can't move up, // last can't move down, everything in between can do either. Call this -// after any insert/remove/reorder in a category's tbody. -function refreshMoveButtonStates(tbody) { - if (!tbody) return; - const rows = [...tbody.querySelectorAll('tr[data-item-id]')]; +// after any insert/remove/reorder in a category's item-rows container. +function refreshMoveButtonStates(rowsContainer) { + if (!rowsContainer) return; + const rows = [...rowsContainer.querySelectorAll('.item-row[data-item-id]')]; rows.forEach((row, index) => { const upBtn = row.querySelector('.btn-move-item[data-direction="up"]'); const downBtn = row.querySelector('.btn-move-item[data-direction="down"]'); @@ -347,18 +347,21 @@ function refreshCategoryMoveButtonStates() { } function itemRowHtml(item, isAvail) { + const badgeHtml = item.badge ? `` : ''; return ` -
No items in this category yet.
'; } } @@ -383,38 +386,39 @@ function updateOrInsertItemRow(item, isNew) { // Update existing row existingRow.dataset.item = JSON.stringify(item); const statusBadge = existingRow.querySelector('.status-badge'); - statusBadge.className = `status-badge ${isAvail ? 'active' : 'inactive'}`; - statusBadge.textContent = isAvail ? '● Available' : '○ Sold Out / Hidden'; + statusBadge.className = `status-badge btn-toggle-item ${isAvail ? 'active' : 'inactive'}`; + statusBadge.textContent = isAvail ? '● Available' : '○ Sold Out'; + statusBadge.title = `Click to mark as ${isAvail ? 'sold out' : 'available'}`; existingRow.querySelector('.item-name-cell').textContent = item.name; existingRow.querySelector('.item-desc-cell').textContent = item.description || ''; existingRow.querySelector('.item-price-cell').textContent = item.price; - const badgeCell = existingRow.querySelector('.item-badge-cell'); - badgeCell.innerHTML = item.badge - ? `${escapeHtml(item.badge)}` - : '—'; + const nameRow = existingRow.querySelector('.item-name-row'); + nameRow.querySelector('.menu-item-badge')?.remove(); + if (item.badge) { + nameRow.insertAdjacentHTML('beforeend', ``); + } - existingRow.querySelector('.btn-toggle-item').textContent = isAvail ? 'Mark Sold Out' : 'Mark Available'; return; } // Item was created or moved to this category — it always joins at the // end (see next_item_sort_order() server-side), so just append it. - const catTable = document.getElementById(`cat-table-${item.category_id}`); - if (!catTable) return; + const rowsContainer = document.getElementById(`item-rows-${item.category_id}`); + if (!rowsContainer) return; - const tbody = catTable.querySelector('tbody'); - tbody.querySelector('.no-items-row')?.remove(); + rowsContainer.querySelector('.item-grid-empty')?.remove(); - const tr = document.createElement('tr'); - tr.id = `item-row-${item.id}`; - tr.dataset.itemId = item.id; - tr.dataset.item = JSON.stringify(item); - tr.innerHTML = itemRowHtml(item, isAvail); + const row = document.createElement('div'); + row.className = 'item-row'; + row.id = `item-row-${item.id}`; + row.dataset.itemId = item.id; + row.dataset.item = JSON.stringify(item); + row.innerHTML = itemRowHtml(item, isAvail); - tbody.appendChild(tr); - refreshMoveButtonStates(tbody); + rowsContainer.appendChild(row); + refreshMoveButtonStates(rowsContainer); } // Static shell for a category panel. Only text-content positions use @@ -424,37 +428,32 @@ function updateOrInsertItemRow(item, isNew) { function categoryPanelHtml(category) { return `${escapeHtml(category.description || '')}
-${escapeHtml(category.description || '')}
| Status | -Item | -Price | -Tag | -Actions | -
|---|---|---|---|---|
| No items in this category yet. | -||||
No items in this category yet.
+= htmlspecialchars($category['description'] ?? '') ?>
-= htmlspecialchars($category['description'] ?? '') ?>
| Status | -Item | -Price | -Tag | -Actions | -
|---|---|---|---|---|
| No items in this category yet. | -||||
| - - ● Available - - ○ Sold Out / Hidden - - | -
+
+
+
+
+ No items in this category yet. + + + $item): ?> +'>
+
+
+
+
+
= htmlspecialchars($item['name']) ?>
-
- = htmlspecialchars($item['description'] ?? '') ?>
-
- |
- - = htmlspecialchars($item['price']) ?> - | -- = htmlspecialchars($item['badge']) ?> - - — + - | -
-
+
+
+
+ = htmlspecialchars($item['description'] ?? '') ?>
+
+
+ = htmlspecialchars($item['price']) ?>
+
+
+
+
+
+
-
- |
-