feat: add admin menu configuration and sidebar improvements

- Add AdminMenuConfig model for per-platform menu customization
- Add menu registry for centralized menu configuration
- Add my-menu-config and platform-menu-config admin pages
- Update sidebar with improved layout and Alpine.js interactions
- Add FrontendType enum for admin/vendor menu separation
- Document self-contained module patterns in session note

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-27 20:34:58 +01:00
parent 32e43efb3c
commit 9a828999fe
14 changed files with 2346 additions and 619 deletions

View File

@@ -212,6 +212,69 @@ function data() {
get isSuperAdmin() {
return this.adminProfile?.is_super_admin === true;
},
// ─────────────────────────────────────────────────────────────────
// Dynamic menu visibility (loaded from API)
// ─────────────────────────────────────────────────────────────────
menuData: null,
menuLoading: false,
visibleMenuItems: new Set(),
async loadMenuConfig(forceReload = false) {
// Don't reload if already loaded (unless forced)
if (!forceReload && (this.menuData || this.menuLoading)) return;
// Skip if apiClient is not available (e.g., on login page)
if (typeof apiClient === 'undefined') {
console.debug('Menu config: apiClient not available');
return;
}
// Skip if not authenticated
if (!localStorage.getItem('admin_token')) {
console.debug('Menu config: no admin_token, skipping');
return;
}
this.menuLoading = true;
try {
this.menuData = await apiClient.get('/admin/menu-config/render/admin');
// Build a set of visible menu item IDs for quick lookup
this.visibleMenuItems = new Set();
for (const section of (this.menuData?.sections || [])) {
for (const item of (section.items || [])) {
this.visibleMenuItems.add(item.id);
}
}
console.debug('Menu config loaded:', this.visibleMenuItems.size, 'items');
} catch (e) {
// Silently fail - menu will show all items as fallback
console.debug('Menu config not loaded, using defaults:', e?.message || e);
} finally {
this.menuLoading = false;
}
},
async reloadSidebarMenu() {
// Force reload the sidebar menu config
this.menuData = null;
this.visibleMenuItems = new Set();
await this.loadMenuConfig(true);
},
isMenuItemVisible(menuItemId) {
// If menu not loaded yet, show all items (fallback to hardcoded)
if (!this.menuData) return true;
return this.visibleMenuItems.has(menuItemId);
},
isSectionVisible(sectionId) {
// If menu not loaded yet, show all sections
if (!this.menuData) return true;
// Check if any item in this section is visible
const section = this.menuData?.sections?.find(s => s.id === sectionId);
return section && section.items && section.items.length > 0;
}
}
}