Phase 1 - Vendor Router Integration: - Wire up vendor module routers in app/api/v1/vendor/__init__.py - Use lazy imports via __getattr__ to avoid circular dependencies Phase 2 - Extract Remaining Modules: - Create 6 new module directories: customers, cms, analytics, messaging, dev_tools, monitoring - Each module has definition.py and route wrappers - Update registry to import from extracted modules Phase 3 - Database Table Migration: - Add PlatformModule junction table for auditable module tracking - Add migration zc2m3n4o5p6q7_add_platform_modules_table.py - Add modules relationship to Platform model - Update ModuleService with JSON-to-junction-table migration Phase 4 - Module-Specific Configuration UI: - Add /api/v1/admin/module-config/* endpoints - Add module-config.html template and JS Phase 5 - Integration Tests: - Add tests/fixtures/module_fixtures.py - Add tests/integration/api/v1/admin/test_modules.py - Add tests/integration/api/v1/modules/test_module_access.py Architecture fixes: - Fix JS-003 errors: use ...data() directly in Alpine components - Fix JS-005 warnings: add init() guards to prevent duplicate init - Fix API-001 errors: add MenuActionResponse Pydantic model - Add FE-008 noqa for dynamic number input in template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
188 lines
6.5 KiB
JavaScript
188 lines
6.5 KiB
JavaScript
// static/admin/js/my-menu-config.js
|
|
// Personal menu configuration for super admins
|
|
//
|
|
// TODO: BUG - Sidebar menu doesn't update immediately after changes.
|
|
// User must navigate to another page to see the updated menu.
|
|
// The issue is that Alpine.js doesn't properly track reactivity for the
|
|
// visibleMenuItems Set in init-alpine.js. Attempted fixes with reloadSidebarMenu()
|
|
// and window.location.reload() didn't work reliably.
|
|
// Possible solutions:
|
|
// 1. Convert visibleMenuItems from Set to plain object for better Alpine reactivity
|
|
// 2. Use Alpine.store() for shared state between components
|
|
// 3. Dispatch a custom event that the sidebar listens for
|
|
// 4. Force re-render of sidebar component after changes
|
|
|
|
const myMenuConfigLog = window.LogConfig?.loggers?.myMenuConfig || window.LogConfig?.createLogger?.('myMenuConfig') || console;
|
|
|
|
function adminMyMenuConfig() {
|
|
return {
|
|
// Inherit base layout functionality from init-alpine.js
|
|
...data(),
|
|
|
|
// Page-specific state
|
|
currentPage: 'my-menu',
|
|
loading: true,
|
|
error: null,
|
|
successMessage: null,
|
|
saving: false,
|
|
|
|
// Data
|
|
menuConfig: null,
|
|
|
|
// Computed grouped items
|
|
get groupedItems() {
|
|
if (!this.menuConfig?.items) return [];
|
|
|
|
// Group items by section
|
|
const sections = {};
|
|
for (const item of this.menuConfig.items) {
|
|
const sectionId = item.section_id;
|
|
if (!sections[sectionId]) {
|
|
sections[sectionId] = {
|
|
id: sectionId,
|
|
label: item.section_label,
|
|
isSuperAdminOnly: item.is_super_admin_only,
|
|
items: [],
|
|
visibleCount: 0
|
|
};
|
|
}
|
|
sections[sectionId].items.push(item);
|
|
if (item.is_visible) {
|
|
sections[sectionId].visibleCount++;
|
|
}
|
|
}
|
|
|
|
// Convert to array and maintain order
|
|
return Object.values(sections);
|
|
},
|
|
|
|
async init() {
|
|
// Guard against multiple initialization
|
|
if (window._adminMyMenuConfigInitialized) {
|
|
myMenuConfigLog.warn('Already initialized, skipping');
|
|
return;
|
|
}
|
|
window._adminMyMenuConfigInitialized = true;
|
|
|
|
myMenuConfigLog.info('=== MY MENU CONFIG PAGE INITIALIZING ===');
|
|
|
|
try {
|
|
await this.loadMenuConfig();
|
|
myMenuConfigLog.info('=== MY MENU CONFIG PAGE INITIALIZED ===');
|
|
} catch (error) {
|
|
myMenuConfigLog.error('Failed to initialize my menu config page:', error);
|
|
this.error = 'Failed to load page data. Please refresh.';
|
|
}
|
|
},
|
|
|
|
async refresh() {
|
|
this.error = null;
|
|
this.successMessage = null;
|
|
await this.loadMenuConfig();
|
|
},
|
|
|
|
async loadMenuConfig() {
|
|
this.loading = true;
|
|
this.error = null;
|
|
|
|
try {
|
|
this.menuConfig = await apiClient.get('/admin/menu-config/user');
|
|
myMenuConfigLog.info('Loaded menu config:', {
|
|
totalItems: this.menuConfig?.total_items,
|
|
visibleItems: this.menuConfig?.visible_items
|
|
});
|
|
} catch (error) {
|
|
myMenuConfigLog.error('Failed to load menu config:', error);
|
|
this.error = error.message || 'Failed to load menu configuration';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
async toggleVisibility(item) {
|
|
if (item.is_mandatory) {
|
|
myMenuConfigLog.warn('Cannot toggle mandatory item:', item.id);
|
|
return;
|
|
}
|
|
|
|
this.saving = true;
|
|
this.error = null;
|
|
this.successMessage = null;
|
|
|
|
const newVisibility = !item.is_visible;
|
|
|
|
try {
|
|
await apiClient.put('/admin/menu-config/user', {
|
|
menu_item_id: item.id,
|
|
is_visible: newVisibility
|
|
});
|
|
|
|
// Update local state
|
|
item.is_visible = newVisibility;
|
|
|
|
// Update counts
|
|
if (newVisibility) {
|
|
this.menuConfig.visible_items++;
|
|
this.menuConfig.hidden_items--;
|
|
} else {
|
|
this.menuConfig.visible_items--;
|
|
this.menuConfig.hidden_items++;
|
|
}
|
|
|
|
myMenuConfigLog.info('Toggled visibility:', item.id, newVisibility);
|
|
|
|
// Reload the page to refresh sidebar
|
|
window.location.reload();
|
|
} catch (error) {
|
|
myMenuConfigLog.error('Failed to toggle visibility:', error);
|
|
this.error = error.message || 'Failed to update menu visibility';
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
async showAll() {
|
|
if (!confirm('This will show all menu items. Continue?')) {
|
|
return;
|
|
}
|
|
|
|
this.saving = true;
|
|
this.error = null;
|
|
this.successMessage = null;
|
|
|
|
try {
|
|
await apiClient.post('/admin/menu-config/user/show-all');
|
|
myMenuConfigLog.info('Showed all menu items');
|
|
|
|
// Reload the page to refresh sidebar
|
|
window.location.reload();
|
|
} catch (error) {
|
|
myMenuConfigLog.error('Failed to show all menu items:', error);
|
|
this.error = error.message || 'Failed to show all menu items';
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
async resetToDefaults() {
|
|
if (!confirm('This will hide all menu items (except mandatory ones). You can then enable the ones you want. Continue?')) {
|
|
return;
|
|
}
|
|
|
|
this.saving = true;
|
|
this.error = null;
|
|
this.successMessage = null;
|
|
|
|
try {
|
|
await apiClient.post('/admin/menu-config/user/reset');
|
|
myMenuConfigLog.info('Reset menu config to defaults');
|
|
|
|
// Reload the page to refresh sidebar
|
|
window.location.reload();
|
|
} catch (error) {
|
|
myMenuConfigLog.error('Failed to reset menu config:', error);
|
|
this.error = error.message || 'Failed to reset menu configuration';
|
|
this.saving = false;
|
|
}
|
|
}
|
|
};
|
|
}
|