/** * Store Roles Management — Alpine.js component * * Provides CRUD for custom roles with a permission matrix UI. * Loaded on /store/{store_code}/team/roles. */ const storeRolesLog = (window.LogConfig && window.LogConfig.createLogger) ? window.LogConfig.createLogger('storeRoles', false) : console; function storeRoles() { storeRolesLog.info('storeRoles() called'); return { // Inherit base layout state ...data(), // Set page identifier currentPage: 'team', // Component state roles: [], loading: true, error: false, saving: false, showRoleModal: false, editingRole: null, roleForm: { name: '', permissions: [] }, permissionCategories: [], presetRoles: ['manager', 'staff', 'support', 'viewer', 'marketing'], async init() { // Guard against multiple initialization if (window._storeRolesInitialized) { storeRolesLog.warn('Already initialized, skipping'); return; } window._storeRolesInitialized = true; // Call parent init to set storeCode from URL const parentInit = data().init; if (parentInit) { await parentInit.call(this); } await this.loadPermissions(); await this.loadRoles(); }, async loadPermissions() { try { const response = await apiClient.get('/store/team/permissions/catalog'); this.permissionCategories = response.categories || []; storeRolesLog.info('Loaded permission catalog:', this.permissionCategories.length, 'categories'); } catch (e) { storeRolesLog.warn('Could not load permission catalog:', e); } }, async loadRoles() { this.loading = true; this.error = false; try { const response = await apiClient.get('/store/team/roles'); this.roles = response.roles || []; } catch (e) { this.error = true; storeRolesLog.error('Error loading roles:', e); } finally { this.loading = false; } }, isPresetRole(name) { return this.presetRoles.includes(name.toLowerCase()); }, openCreateModal() { this.editingRole = null; this.roleForm = { name: '', permissions: [] }; this.showRoleModal = true; }, openEditModal(role) { this.editingRole = role; this.roleForm = { name: role.name, permissions: [...(role.permissions || [])], }; this.showRoleModal = true; }, togglePermission(permId) { const idx = this.roleForm.permissions.indexOf(permId); if (idx >= 0) { this.roleForm.permissions.splice(idx, 1); } else { this.roleForm.permissions.push(permId); } }, toggleCategory(category) { const perms = category.permissions || []; const permIds = perms.map(p => p.id); const allSelected = permIds.every(id => this.roleForm.permissions.includes(id)); if (allSelected) { this.roleForm.permissions = this.roleForm.permissions.filter(id => !permIds.includes(id)); } else { for (const id of permIds) { if (!this.roleForm.permissions.includes(id)) { this.roleForm.permissions.push(id); } } } }, isCategoryFullySelected(category) { const perms = category.permissions || []; return perms.length > 0 && perms.every(p => this.roleForm.permissions.includes(p.id)); }, async saveRole() { this.saving = true; try { if (this.editingRole) { await apiClient.put(`/store/team/roles/${this.editingRole.id}`, this.roleForm); } else { await apiClient.post('/store/team/roles', this.roleForm); } this.showRoleModal = false; Utils.showToast('Role saved successfully', 'success'); await this.loadRoles(); } catch (e) { storeRolesLog.error('Error saving role:', e); Utils.showToast(e.message || 'Failed to save role', 'error'); } finally { this.saving = false; } }, async confirmDelete(role) { if (!confirm(`Delete role "${role.name}"? This cannot be undone.`)) return; try { await apiClient.delete(`/store/team/roles/${role.id}`); Utils.showToast('Role deleted successfully', 'success'); await this.loadRoles(); } catch (e) { storeRolesLog.error('Error deleting role:', e); Utils.showToast(e.message || 'Failed to delete role', 'error'); } }, }; }