UNPKG

besper-frontend-site-dev-main

Version:

Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment

394 lines (345 loc) 12 kB
/** * Workspace Admin Management Component * Handles admin selection and management for workspaces */ import MultiSelectDropdown from './MultiSelectDropdown.js'; class WorkspaceAdminManager { constructor(pageOperatorsService, authService) { this.operatorsService = pageOperatorsService; this.authService = authService; this.currentWorkspaceId = null; this.multiSelect = null; this.workspaces = []; this.loading = false; } /** * Show admin management modal for a workspace */ showAdminModal(workspaceId, workspaceName) { this.currentWorkspaceId = workspaceId; const modal = document.createElement('div'); modal.className = 'modal-overlay admin-modal-overlay'; modal.innerHTML = ` <div class="modal admin-modal"> <div class="modal-header"> <h2>Manage Administrators</h2> <div class="modal-subtitle">Workspace: ${this.escapeHtml(workspaceName)}</div> <button class="close-modal" type="button"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> </svg> </button> </div> <div class="modal-body"> <div class="admin-section"> <h3>Current Administrators</h3> <div id="currentAdmins" class="current-admins"> <div class="loading-skeleton"> <div class="skeleton-item"></div> <div class="skeleton-item"></div> <div class="skeleton-item"></div> </div> </div> </div> <div class="admin-section"> <h3>Add Administrators</h3> <div class="form-group"> <label>Search and Select Users</label> <div id="adminSelector"></div> </div> <div class="filter-controls"> <div class="form-group"> <label for="workspaceFilter">Filter by Workspace</label> <select id="workspaceFilter" class="form-control"> <option value="">All Workspaces</option> <!-- Workspace options will be populated --> </select> </div> </div> </div> </div> <div class="modal-footer"> <button class="btn btn-outline close-modal" type="button">Cancel</button> <button class="btn btn-primary" id="saveAdmins" type="button">Save Changes</button> </div> </div> `; document.body.appendChild(modal); this.setupModalListeners(modal); this.initializeAdminSelector(); this.loadCurrentAdmins(); this.loadWorkspaces(); } setupModalListeners(modal) { // Close modal listeners modal.querySelectorAll('.close-modal').forEach(btn => { btn.addEventListener('click', () => this.closeModal()); }); // Click outside to close modal.addEventListener('click', e => { if (e.target === modal) { this.closeModal(); } }); // Save changes modal.querySelector('#saveAdmins').addEventListener('click', () => { this.saveAdminChanges(); }); // Workspace filter modal.querySelector('#workspaceFilter').addEventListener('change', e => { this.filterUsers('', e.target.value); }); } initializeAdminSelector() { const container = document.getElementById('adminSelector'); if (!container) return; this.multiSelect = new MultiSelectDropdown(container, { placeholder: 'Search for users to add as administrators...', searchPlaceholder: 'Type name or email to search...', onSelectionChange: selectedItems => { this.updateSelectedAdmins(selectedItems); }, onSearch: query => { const workspaceFilter = document.getElementById('workspaceFilter')?.value || ''; this.filterUsers(query, workspaceFilter); }, debounceDelay: 300, }); // Initial load of users this.filterUsers('', ''); } async loadCurrentAdmins() { try { this.setCurrentAdminsLoading(true); const response = await this.operatorsService.callOperator( 'workspace_admin_management', 'get_workspace_admins', { authData: this.authService.getAuthData(), workspace_id: this.currentWorkspaceId, } ); if (response.success) { this.renderCurrentAdmins(response.data.admins || []); } else { this.showError('Failed to load current administrators'); } } catch (error) { console.error('Error loading current admins:', error); this.showError('Failed to load current administrators'); } finally { this.setCurrentAdminsLoading(false); } } async loadWorkspaces() { try { const response = await this.operatorsService.callOperator( 'workspace_management', 'list_workspaces', { authData: this.authService.getAuthData(), } ); if (response.success) { this.workspaces = response.data.workspaces || []; this.populateWorkspaceFilter(); } } catch (error) { console.error('Error loading workspaces:', error); } } populateWorkspaceFilter() { const select = document.getElementById('workspaceFilter'); if (!select) return; const workspaceOptions = this.workspaces .map( workspace => `<option value="${workspace.id}">${this.escapeHtml(workspace.name)}</option>` ) .join(''); select.innerHTML = ` <option value="">All Workspaces</option> ${workspaceOptions} `; } async filterUsers(searchQuery = '', workspaceFilter = '') { try { if (this.multiSelect) { this.multiSelect.container.classList.add('loading'); } const response = await this.operatorsService.callOperator( 'workspace_admin_management', 'get_users_for_admin_selection', { authData: this.authService.getAuthData(), search_query: searchQuery, workspace_filter: workspaceFilter, } ); if (response.success) { const users = response.data.users || []; if (this.multiSelect) { this.multiSelect.setFilteredItems(users); } } else { console.error('Failed to filter users:', response.error); } } catch (error) { console.error('Error filtering users:', error); } finally { if (this.multiSelect) { this.multiSelect.container.classList.remove('loading'); } } } renderCurrentAdmins(admins) { const container = document.getElementById('currentAdmins'); if (!container) return; if (admins.length === 0) { container.innerHTML = ` <div class="no-admins"> <div class="no-admins-icon">👥</div> <div class="no-admins-text">No administrators assigned</div> <div class="no-admins-subtitle">Add administrators to receive notifications about this workspace</div> </div> `; return; } const adminsHtml = admins .map( admin => ` <div class="admin-item" data-admin-id="${admin.id}"> <div class="admin-info"> <div class="admin-name">${this.escapeHtml(admin.name)}</div> <div class="admin-details"> ${admin.email ? `<span class="admin-email">${this.escapeHtml(admin.email)}</span>` : ''} ${admin.workspace_name ? `<span class="admin-workspace">${this.escapeHtml(admin.workspace_name)}</span>` : ''} </div> </div> <button class="remove-admin" data-admin-id="${admin.id}" type="button"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M12 4L4 12M4 4L12 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> </button> </div> ` ) .join(''); container.innerHTML = adminsHtml; // Add remove listeners container.addEventListener('click', e => { if (e.target.closest('.remove-admin')) { const adminId = e.target.closest('.remove-admin').dataset.adminId; this.removeCurrentAdmin(adminId); } }); } removeCurrentAdmin(adminId) { const adminElement = document.querySelector(`[data-admin-id="${adminId}"]`); if (adminElement) { adminElement.remove(); } // Check if no admins left const container = document.getElementById('currentAdmins'); if (container && container.children.length === 0) { this.renderCurrentAdmins([]); } } updateSelectedAdmins(_selectedItems) { // This is called when multiselect selection changes // We can provide visual feedback here if needed } async saveAdminChanges() { try { this.setSaveLoading(true); // Get current admins that weren't removed const currentAdminElements = document.querySelectorAll( '#currentAdmins .admin-item' ); const currentAdminIds = Array.from(currentAdminElements).map( el => el.dataset.adminId ); // Get newly selected admins const selectedItems = this.multiSelect ? this.multiSelect.getSelectedItems() : []; const newAdminIds = selectedItems.map(item => item.id); // Combine both lists, removing duplicates const allAdminIds = [...new Set([...currentAdminIds, ...newAdminIds])]; const response = await this.operatorsService.callOperator( 'workspace_admin_management', 'update_workspace_admins', { authData: this.authService.getAuthData(), workspace_id: this.currentWorkspaceId, admin_user_ids: allAdminIds, } ); if (response.success) { this.showSuccess('Administrators updated successfully'); this.closeModal(); // Emit event for parent component to refresh if needed this.dispatchEvent('adminsUpdated', { workspaceId: this.currentWorkspaceId, adminCount: allAdminIds.length, }); } else { this.showError(response.error || 'Failed to update administrators'); } } catch (error) { console.error('Error saving admin changes:', error); this.showError('Failed to update administrators'); } finally { this.setSaveLoading(false); } } setCurrentAdminsLoading(loading) { const container = document.getElementById('currentAdmins'); if (!container) return; if (loading) { container.innerHTML = ` <div class="loading-skeleton"> <div class="skeleton-item"></div> <div class="skeleton-item"></div> <div class="skeleton-item"></div> </div> `; } } setSaveLoading(loading) { const saveBtn = document.getElementById('saveAdmins'); if (saveBtn) { saveBtn.disabled = loading; saveBtn.textContent = loading ? 'Saving...' : 'Save Changes'; } } closeModal() { const modal = document.querySelector('.admin-modal-overlay'); if (modal) { modal.remove(); } this.multiSelect = null; this.currentWorkspaceId = null; } showError(message) { // Use existing notification system or console for now console.error(message); // TODO: Integrate with main app's notification system } showSuccess(message) { // Use existing notification system or console for now console.log(message); // TODO: Integrate with main app's notification system } dispatchEvent(eventName, detail) { const event = new CustomEvent(`workspace-admin-${eventName}`, { detail }); document.dispatchEvent(event); } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } export default WorkspaceAdminManager;