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
JavaScript
/**
* 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;