// Menu Management admin page: category/item CRUD via fetch, modal handling. // The CSRF token is handed off from PHP via
so // this file stays pure static JS with no server-side templating in it. const csrfToken = document.body.dataset.csrfToken; // Item Modal helpers const itemModal = document.getElementById('modal-item-backdrop'); const catModal = document.getElementById('modal-cat-backdrop'); function openItemModal(itemData = null) { const title = document.getElementById('modal-item-title'); const idField = document.getElementById('item-field-id'); const nameField = document.getElementById('item-field-name'); const catField = document.getElementById('item-field-cat'); const priceField = document.getElementById('item-field-price'); const badgeField = document.getElementById('item-field-badge'); const descField = document.getElementById('item-field-desc'); const sortField = document.getElementById('item-field-sort'); const availField = document.getElementById('item-field-available'); if (itemData) { title.textContent = '✏️ Edit Menu Item'; idField.value = itemData.id || 0; nameField.value = itemData.name || ''; catField.value = itemData.category_id || catField.options[0]?.value || 0; priceField.value = itemData.price || ''; badgeField.value = itemData.badge || ''; descField.value = itemData.description || ''; sortField.value = itemData.sort_order || 0; availField.checked = parseInt(itemData.is_available, 10) === 1; } else { title.textContent = '✨ Add Menu Item'; idField.value = 0; nameField.value = ''; priceField.value = ''; badgeField.value = ''; descField.value = ''; sortField.value = 0; availField.checked = true; } itemModal.classList.add('is-open'); nameField.focus(); } function closeItemModal() { itemModal.classList.remove('is-open'); } function openCategoryModal(catData = null) { const title = document.getElementById('modal-cat-title'); const idField = document.getElementById('cat-field-id'); const nameField = document.getElementById('cat-field-name'); const descField = document.getElementById('cat-field-desc'); const sortField = document.getElementById('cat-field-sort'); if (catData) { title.textContent = '✏️ Edit Category'; idField.value = catData.id || 0; nameField.value = catData.name || ''; descField.value = catData.description || ''; sortField.value = catData.sort_order || 0; } else { title.textContent = '✨ Add Category'; idField.value = 0; nameField.value = ''; descField.value = ''; sortField.value = 0; } catModal.classList.add('is-open'); nameField.focus(); } function closeCategoryModal() { catModal.classList.remove('is-open'); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text ?? ''; return div.innerHTML; } async function postForm(formData) { const response = await fetch('menu.php', { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }); return response.json(); } // Modal listeners document.getElementById('btn-new-item').addEventListener('click', () => openItemModal()); document.getElementById('btn-new-cat').addEventListener('click', () => openCategoryModal()); document.getElementById('btn-create-first-cat')?.addEventListener('click', () => openCategoryModal()); document.getElementById('btn-close-item-modal').addEventListener('click', closeItemModal); document.getElementById('btn-cancel-item').addEventListener('click', closeItemModal); document.getElementById('btn-close-cat-modal').addEventListener('click', closeCategoryModal); document.getElementById('btn-cancel-cat').addEventListener('click', closeCategoryModal); // Form submit: Save Item (Async Fetch) document.getElementById('form-item').addEventListener('submit', async (e) => { e.preventDefault(); const saveBtn = document.getElementById('btn-save-item'); saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; try { const res = await postForm(new FormData(e.target)); if (res.success) { closeItemModal(); updateOrInsertItemRow(res.item, res.is_new); } else { alert(res.error || 'Failed to save item.'); } } catch { alert('Network error. Please try again.'); } finally { saveBtn.disabled = false; saveBtn.textContent = 'Save Item'; } }); // Form submit: Save Category (Async Fetch) document.getElementById('form-cat').addEventListener('submit', async (e) => { e.preventDefault(); const saveBtn = document.getElementById('btn-save-cat'); saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; try { const res = await postForm(new FormData(e.target)); if (res.success) { closeCategoryModal(); updateOrInsertCategoryPanel(res.category); } else { alert(res.error || 'Failed to save category.'); } } catch { alert('Network error. Please try again.'); } finally { saveBtn.disabled = false; saveBtn.textContent = 'Save Category'; } }); // Delegate Table Actions: Edit, Toggle Availability, Delete document.getElementById('categories-container').addEventListener('click', async (e) => { const { target } = e; // Edit Item if (target.classList.contains('btn-edit-item')) { const row = target.closest('tr'); openItemModal(JSON.parse(row.dataset.item)); return; } // Edit Category if (target.classList.contains('btn-edit-cat')) { openCategoryModal({ id: target.dataset.id, name: target.dataset.name, description: target.dataset.desc, sort_order: target.dataset.sort }); return; } // Toggle Availability (Mark Sold Out / Mark Available) if (target.classList.contains('btn-toggle-item')) { const row = target.closest('tr'); const statusBadge = row.querySelector('.status-badge'); target.disabled = true; const fd = new FormData(); fd.append('action', 'toggle_item'); fd.append('item_id', target.dataset.id); fd.append('csrf_token', csrfToken); 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; const currentData = JSON.parse(row.dataset.item); currentData.is_available = data.is_available ? 1 : 0; row.dataset.item = JSON.stringify(currentData); } } catch { alert('Failed to update status.'); } finally { target.disabled = false; } return; } // 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 fd = new FormData(); fd.append('action', 'delete_item'); fd.append('item_id', target.dataset.id); fd.append('csrf_token', csrfToken); try { const data = await postForm(fd); if (data.success && row) { row.style.transition = 'opacity 0.2s ease, transform 0.2s ease'; row.style.opacity = '0'; row.style.transform = 'translateX(-10px)'; setTimeout(() => row.remove(), 200); } } catch { alert('Failed to delete item.'); } return; } // Delete Category if (target.classList.contains('btn-delete-cat')) { if (!confirm('Are you sure you want to delete this category and ALL its menu items?')) return; const panel = document.getElementById(`cat-panel-${target.dataset.id}`); const fd = new FormData(); fd.append('action', 'delete_category'); fd.append('category_id', target.dataset.id); fd.append('csrf_token', csrfToken); try { const data = await postForm(fd); if (data.success && panel) { panel.style.transition = 'opacity 0.2s ease, transform 0.2s ease'; panel.style.opacity = '0'; panel.style.transform = 'translateY(-10px)'; setTimeout(() => panel.remove(), 200); } } catch { alert('Failed to delete category.'); } } }); function updateOrInsertItemRow(item, isNew) { const isAvail = parseInt(item.is_available, 10) === 1; const existingRow = document.getElementById(`item-row-${item.id}`); if (existingRow) { // 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'; existingRow.querySelector('.item-name-cell').textContent = item.name; existingRow.querySelector('.item-desc-cell').textContent = item.description || ''; existingRow.querySelector('.item-price-cell').textContent = item.price; existingRow.querySelector('.item-sort-cell').textContent = item.sort_order || 0; const badgeCell = existingRow.querySelector('.item-badge-cell'); badgeCell.innerHTML = item.badge ? `${escapeHtml(item.badge)}` : '—'; existingRow.querySelector('.btn-toggle-item').textContent = isAvail ? 'Mark Sold Out' : 'Mark Available'; return; } // Item was created or moved — insert into matching category table const catTable = document.getElementById(`cat-table-${item.category_id}`); if (!catTable) return; const tbody = catTable.querySelector('tbody'); tbody.querySelector('.no-items-row')?.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 = `${escapeHtml(category.description || '')}
| Status | Item | Price | Tag | Order | Actions |
|---|---|---|---|---|---|
| No items in this category yet. | |||||