/** Menu Management admin page — hierarchical menu item list + up/down
* reorder within each sibling group, icon + parent (one level of nesting). */
let allMenuItems = [];
let allMenuModules = [];
async function loadMenuModules() {
try {
const data = await ddaApi('GET', '/api/dda/rbac/modules');
allMenuModules = data.modules || [];
const sel = document.getElementById('menu-item-module');
if (sel) {
sel.innerHTML = '' +
allMenuModules.map((m) => ``).join('');
}
const keysList = document.getElementById('module-keys-list');
if (keysList) {
keysList.innerHTML = allMenuModules.length
? allMenuModules.map((m) => `${escapeHtml(m.key)}`).join('')
: '
No modules yet.
'; } } catch (_) { // Module select / keys list just stay at their defaults if this fails. } } async function loadMenuItems() { const el = document.getElementById('menu-item-list'); try { const data = await ddaApi('GET', '/api/dda/rbac/menu-items'); allMenuItems = (data.menuItems || []).sort((a, b) => a.sortOrder - b.sortOrder); renderMenuItemList(); } catch (err) { el.innerHTML = `Could not load menu items: ${escapeHtml(err.message || 'error')}
`; } } function moduleNameFor(moduleId) { const m = allMenuModules.find((x) => x.id === moduleId); return m ? m.name : null; } function siblingGroup(parentId) { return allMenuItems.filter((i) => i.parentId === parentId).sort((a, b) => a.sortOrder - b.sortOrder); } function renderItemRow(item, isChild) { const group = siblingGroup(item.parentId); const idx = group.findIndex((i) => i.id === item.id); return ` `; } function renderMenuItemList() { const el = document.getElementById('menu-item-list'); if (!allMenuItems.length) { el.innerHTML = 'No menu items yet.
'; return; } const topLevel = siblingGroup(null); el.innerHTML = topLevel.map((item) => { const children = siblingGroup(item.id); return renderItemRow(item, false) + children.map((c) => renderItemRow(c, true)).join(''); }).join(''); el.querySelectorAll('[data-move]').forEach((btn) => { btn.addEventListener('click', () => moveMenuItem(Number(btn.closest('.dda-menu-item-row').dataset.itemId), btn.dataset.move)); }); el.querySelectorAll('[data-edit-item]').forEach((btn) => { btn.addEventListener('click', () => openMenuItemModal(Number(btn.dataset.editItem))); }); el.querySelectorAll('[data-del-item]').forEach((btn) => { btn.addEventListener('click', () => deleteMenuItem(Number(btn.dataset.delItem))); }); } async function moveMenuItem(itemId, direction) { const item = allMenuItems.find((i) => i.id === itemId); const group = siblingGroup(item.parentId); const idx = group.findIndex((i) => i.id === itemId); const swapWith = direction === 'up' ? idx - 1 : idx + 1; if (swapWith < 0 || swapWith >= group.length) return; [group[idx], group[swapWith]] = [group[swapWith], group[idx]]; renderMenuItemList(); try { await ddaApi('POST', '/api/dda/rbac/menu-items/reorder', { body: JSON.stringify({ orderedIds: group.map((i) => i.id) }), }); group.forEach((i, order) => { i.sortOrder = order; }); } catch (err) { showDdaError(err.message); await loadMenuItems(); } } function populateParentSelect(excludeId) { const sel = document.getElementById('menu-item-parent'); if (!sel) return; const topLevel = allMenuItems.filter((i) => i.parentId === null && i.id !== excludeId); sel.innerHTML = '' + topLevel.map((i) => ``).join(''); } function openMenuItemModal(itemId) { const modal = document.getElementById('menu-item-modal'); const title = document.getElementById('menu-item-modal-title'); const item = allMenuItems.find((i) => i.id === itemId); populateParentSelect(itemId); document.getElementById('menu-item-id').value = item ? item.id : ''; document.getElementById('menu-item-label').value = item ? item.label : ''; document.getElementById('menu-item-icon').value = item ? item.icon || '' : ''; document.getElementById('menu-item-url').value = item ? item.url : ''; document.getElementById('menu-item-module').value = item && item.moduleId ? item.moduleId : ''; document.getElementById('menu-item-parent').value = item && item.parentId ? item.parentId : ''; document.getElementById('menu-item-active').checked = item ? item.isActive : true; title.textContent = item ? 'Edit Menu Item' : 'Add Menu Item'; modal.classList.remove('hidden'); } document.getElementById('btn-new-menu-item')?.addEventListener('click', () => openMenuItemModal(null)); document.getElementById('menu-item-modal-close')?.addEventListener('click', () => document.getElementById('menu-item-modal').classList.add('hidden')); document.getElementById('menu-item-modal')?.addEventListener('click', (e) => { if (e.target.id === 'menu-item-modal') document.getElementById('menu-item-modal').classList.add('hidden'); }); document.getElementById('menu-item-form')?.addEventListener('submit', async (e) => { e.preventDefault(); hideDdaError(); const id = document.getElementById('menu-item-id').value; const moduleVal = document.getElementById('menu-item-module').value; const parentVal = document.getElementById('menu-item-parent').value; const body = { label: document.getElementById('menu-item-label').value.trim(), icon: document.getElementById('menu-item-icon').value.trim(), url: document.getElementById('menu-item-url').value.trim(), moduleId: moduleVal ? Number(moduleVal) : null, parentId: parentVal ? Number(parentVal) : null, isActive: document.getElementById('menu-item-active').checked, }; try { if (id) { await ddaApi('PUT', `/api/dda/rbac/menu-items/${id}`, { body: JSON.stringify(body) }); } else { await ddaApi('POST', '/api/dda/rbac/menu-items', { body: JSON.stringify(body) }); } document.getElementById('menu-item-modal').classList.add('hidden'); showDdaSuccess('Menu item saved.'); await loadMenuItems(); } catch (err) { showDdaError(err.message); } }); async function deleteMenuItem(itemId) { if (!confirm('Delete this menu item?')) return; try { await ddaApi('DELETE', `/api/dda/rbac/menu-items/${itemId}`); showDdaSuccess('Menu item deleted.'); await loadMenuItems(); } catch (err) { showDdaError(err.message); } } document.addEventListener('DOMContentLoaded', async () => { await loadMenuModules(); await loadMenuItems(); });