UNPKG

besper-frontend-site-dev-main

Version:

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

549 lines (486 loc) 19.6 kB
/** * Manage Workspace Page JavaScript - Pure JS with server-side template loading * Manages organizational workspaces and their hierarchies * Uses TemplateLoaderService for HTML/CSS from server-side storage */ class ManageWorkspacePage { constructor(options = {}) { this.options = { containerId: 'besper-site-content', environment: 'dev', ...options, }; this.initialized = false; this.authService = this.getAuthService(); this.isAuthenticated = false; // Initialize template loader for server-side assets this.templateLoader = new window.TemplateLoaderService({ environment: this.options.environment, branch: this.options.branch || 'main', }); } /** * Get authentication service from global scope or create simple fallback */ getAuthService() { // Try to access global token auth service if available if (typeof window !== 'undefined' && window.tokenAuthService) { return window.tokenAuthService; } // Fallback implementation using global window.auth return { isUserAuthenticated: () => { return ( typeof window !== 'undefined' && window.auth && typeof window.auth.getToken === 'function' && !!window.auth.getToken() ); }, getToken: () => { if ( typeof window !== 'undefined' && window.auth && typeof window.auth.getToken === 'function' ) { return window.auth.getToken(); } return null; }, getUserPermission: key => { if (typeof window === 'undefined') return null; const mappings = { contactId: window.contact_id || window.user_contactid, userName: window.user_name, name: window.user_name, userEmail: window.user_email, email: window.user_email, workspaceId: window.workspace_id, accountId: window.account_id, subscriptionId: window.subscription_id, }; return mappings[key] || null; }, }; } /** * Get root API endpoint */ getRootApiEndpoint() { const baseEndpoint = this.getBaseAPIEndpoint(); return `${baseEndpoint}/api`; } /** * Get base API endpoint from environment */ getBaseAPIEndpoint() { // Use the build-time configured endpoint if ( typeof process !== 'undefined' && process.env && process.env.API_ENDPOINT ) { return process.env.API_ENDPOINT; } // Fallback for runtime detection if (typeof window !== 'undefined') { // Check if endpoint is provided in window object if (window.bSiteApiEndpoint) { return window.bSiteApiEndpoint; } // Development fallback if (window.location.hostname === 'localhost') { return process.env.API_ENDPOINT; } } // Default production endpoint return process.env.API_ENDPOINT; } /** * Initialize the workspace management page with server-side template architecture * @param {Object} data - Data object (legacy parameter, not used) * @param {Object} options - Options including serverSideTemplate flag */ async initialize(_data = {}, _options = {}) { if (this.initialized) return; try { // Load template and styles from server-side storage console.log('[LOADING] Loading page assets from server-side storage...'); const { template, styles } = await this.templateLoader.loadPageAssets('manage-workspace'); // Render the server-side template immediately const container = document.getElementById(this.options.containerId); if (container) { container.innerHTML = template; console.log( '[SUCCESS] Server-side template rendered for professional UI' ); } // Apply styles if available if (styles) { let styleElement = document.getElementById( 'besper-manage-workspace-styles' ); if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = 'besper-manage-workspace-styles'; document.head.appendChild(styleElement); } styleElement.textContent = styles; console.log('[SUCCESS] Server-side styles applied for professional UI'); } // Setup JavaScript interactions on the rendered template this.setupInteractionsOnServerTemplate(); this.initialized = true; // DEFERRED: Initialize authentication features in background // This runs completely in background without blocking UI this.initializeAuthenticationFeaturesInBackground(); } catch (error) { console.error('Error initializing workspace management page:', error); console.log('[WARN] Fallback to client-side rendering'); // Fallback: Show the workspace management UI using client-side fallback this.renderFallbackUI(); this.setupInteractions(); } } /** * Render fallback UI when server-side template fails */ renderFallbackUI() { const container = document.getElementById(this.options.containerId); if (!container) return; // Clear any existing loading indicators container.innerHTML = ''; // Show immediate workspace management interface container.innerHTML = ` <div style="background: #f8f9fa; color: #022d54; font-family: Arial, sans-serif;"> <div style="max-width: 1200px; margin: 0 auto; padding: 2rem 1rem;"> <!-- Header --> <div style="margin-bottom: 2rem;"> <h1 style="color: #022d54; font-weight: 300; font-size: 28px; margin-bottom: 0.5rem;">Workspace Management</h1> <p style="color: #6c757d; font-size: 14px; margin: 0;">Manage organizational workspaces and their hierarchies</p> </div> <!-- Action Buttons --> <div style="display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 2rem;"> <button style="padding: 10px 20px; border: 1px solid #022d54; border-radius: 6px; background: white; color: #022d54; cursor: pointer; font-size: 14px;"> 📤 Export </button> <button style="padding: 10px 20px; border: none; border-radius: 6px; background: #022d54; color: white; cursor: pointer; font-size: 14px;"> ➕ Create Workspace </button> </div> <!-- Stats Cards with Skeleton Loading --> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-bottom: 2rem;"> <div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; padding: 1.5rem;"> <div style="color: #6c757d; font-size: 12px; text-transform: uppercase; margin-bottom: 0.5rem;">Total Workspaces</div> <div style="color: #022d54; font-size: 24px; font-weight: 600; margin-bottom: 0.5rem;"> <div class="skeleton-loader" style="height: 24px; background: #e0e0e0; border-radius: 4px; width: 40px;"></div> </div> <div style="color: #6c757d; font-size: 14px;">Active workspaces</div> </div> <div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; padding: 1.5rem;"> <div style="color: #6c757d; font-size: 12px; text-transform: uppercase; margin-bottom: 0.5rem;">Total Users</div> <div style="color: #022d54; font-size: 24px; font-weight: 600; margin-bottom: 0.5rem;"> <div class="skeleton-loader" style="height: 24px; background: #e0e0e0; border-radius: 4px; width: 60px;"></div> </div> <div style="color: #6c757d; font-size: 14px;">Across all workspaces</div> </div> <div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; padding: 1.5rem;"> <div style="color: #6c757d; font-size: 12px; text-transform: uppercase; margin-bottom: 0.5rem;">Total Licenses</div> <div style="color: #022d54; font-size: 24px; font-weight: 600; margin-bottom: 0.5rem;"> <div class="skeleton-loader" style="height: 24px; background: #e0e0e0; border-radius: 4px; width: 50px;"></div> </div> <div style="color: #6c757d; font-size: 14px;">License allocation</div> </div> <div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; padding: 1.5rem;"> <div style="color: #6c757d; font-size: 12px; text-transform: uppercase; margin-bottom: 0.5rem;">Cost Pools</div> <div style="color: #022d54; font-size: 24px; font-weight: 600; margin-bottom: 0.5rem;"> <div class="skeleton-loader" style="height: 24px; background: #e0e0e0; border-radius: 4px; width: 30px;"></div> </div> <div style="color: #6c757d; font-size: 14px;">Unique cost centers</div> </div> </div> <!-- Workspaces Table --> <div style="background: white; border-radius: 8px; border: 1px solid #e0e0e0; overflow: hidden;"> <div style="padding: 1rem; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center;"> <h3 style="margin: 0; color: #022d54; font-size: 16px; font-weight: 500;">All Workspaces</h3> <input type="text" placeholder="Search workspaces..." style="padding: 8px 12px; border: 1px solid #e0e0e0; border-radius: 4px; font-size: 14px; width: 200px;"> </div> <div id="workspace-table-content" style="padding: 2rem;"> <!-- Skeleton Loading Animation --> <div class="skeleton-loader" style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem;"></div> <div class="skeleton-loader" style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem; width: 80%;"></div> <div class="skeleton-loader" style="height: 20px; background: #e0e0e0; border-radius: 4px; margin-bottom: 1rem; width: 60%;"></div> <div class="skeleton-loader" style="height: 100px; background: #e0e0e0; border-radius: 4px;"></div> </div> </div> </div> </div> <!-- Skeleton Loading Animation --> <style> .skeleton-loader { animation: pulse 1.5s ease-in-out infinite; } @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } } </style> `; } /** * Setup interactions immediately */ setupInteractions() { // Basic interaction setup without waiting for authentication console.log('Workspace Management interactions ready'); } /** * Setup interactions on server-side rendered template * This method is called when the template was loaded from server-side storage */ setupInteractionsOnServerTemplate() { console.log('Setting up interactions on server-side template...'); // The HTML is already rendered from server-side storage // Just add event listeners and JavaScript behavior // Add click handlers for action buttons this.setupActionButtonHandlers(); // Setup table interactions this.setupTableInteractions(); // Setup responsive behavior this.setupResponsiveBehavior(); console.log('[SUCCESS] Server-side template interactions ready'); } /** * Setup action button handlers */ setupActionButtonHandlers() { // Add event listeners for buttons in the header actions const headerActions = document.querySelector( '.workspace-header .header-actions' ); if (headerActions) { headerActions.addEventListener('click', e => { const button = e.target.closest('button'); if (button) { console.log('Action button clicked:', button.textContent); // Handle button actions here } }); } } /** * Setup table interactions */ setupTableInteractions() { // Add event listeners for table actions const tableContainer = document.querySelector('.table-container'); if (tableContainer) { tableContainer.addEventListener('click', e => { const actionBtn = e.target.closest('.skeleton-btn-sm, .action-btn'); if (actionBtn) { console.log('Table action clicked'); // Handle table actions here } }); } } /** * Setup responsive behavior */ setupResponsiveBehavior() { // Add responsive event listeners if needed console.log('Responsive behavior setup complete'); } /** * Initialize authentication features completely in background - NO UI BLOCKING */ async initializeAuthenticationFeaturesInBackground() { // Use requestIdleCallback for true background processing const initializeAuth = () => { try { // Check authentication status without blocking this.isAuthenticated = this.authService.isUserAuthenticated(); if (this.isAuthenticated) { // Load workspace data in background this.loadWorkspaceDataInBackground(); } else { this.showUnauthenticatedView(); } } catch (error) { console.warn( 'Background authentication features initialization failed:', error ); // Continue silently - authentication is not critical for UI display } }; // Use requestIdleCallback for non-blocking background execution if (typeof requestIdleCallback !== 'undefined') { requestIdleCallback(initializeAuth, { timeout: 5000 }); } else { // Fallback for browsers without requestIdleCallback setTimeout(initializeAuth, 50); } } /** * Load workspace data in background */ async loadWorkspaceDataInBackground() { try { console.log('Loading workspace management data in background...'); // TODO: Replace with actual workspace data loading // Example: // const workspaceData = await this.fetchWorkspaceData(); // this.updateStatsWithData(workspaceData); // this.updateTableWithData(workspaceData); // For now, simulate data loading and update UI setTimeout(() => { this.updateUIWithWorkspaceData(); }, 1000); } catch (error) { console.error('Background workspace data loading failed:', error); this.showDataLoadingError(); } } /** * Update UI with actual workspace data (replace skeleton loading) */ updateUIWithWorkspaceData() { console.log('[Workspace] 📊 Updating skeleton elements with real data'); // Update stats with real data this.updateStatsData(); // Update table with real data this.updateTableData(); // Transition skeleton elements to loaded state this.transitionSkeletonToLoaded(); } /** * Update statistics cards with real data */ updateStatsData() { const statValues = document.querySelectorAll('.stat-value .skeleton-text'); const statData = ['12', '1,247', '486', '8']; statValues.forEach((element, index) => { if (index < statData.length) { element.textContent = statData[index]; } }); } /** * Update table with real workspace data */ updateTableData() { const tableBody = document.getElementById('workspaces-tbody'); if (!tableBody) return; // Replace skeleton rows with real data tableBody.innerHTML = ` <tr> <td>Global Organization</td> <td>—</td> <td>1,247</td> <td>Active</td> <td>2023-01-15</td> <td> <button class="bsp-btn bsp-btn-sm" title="View">👁️</button> <button class="bsp-btn bsp-btn-sm" title="Edit">✏️</button> <button class="bsp-btn bsp-btn-sm" title="Users">👥</button> </td> </tr> <tr> <td style="padding-left: 2rem;">└ Enterprise Division</td> <td>Global Organization</td> <td>342</td> <td>Active</td> <td>2023-02-20</td> <td> <button class="bsp-btn bsp-btn-sm" title="View">👁️</button> <button class="bsp-btn bsp-btn-sm" title="Edit">✏️</button> <button class="bsp-btn bsp-btn-sm" title="Users">👥</button> </td> </tr> <tr> <td style="padding-left: 2rem;">└ Product Development</td> <td>Global Organization</td> <td>186</td> <td>Active</td> <td>2023-03-10</td> <td> <button class="bsp-btn bsp-btn-sm" title="View">👁️</button> <button class="bsp-btn bsp-btn-sm" title="Edit">✏️</button> <button class="bsp-btn bsp-btn-sm" title="Users">👥</button> </td> </tr> `; } /** * Transition skeleton elements to loaded state with smooth animation */ transitionSkeletonToLoaded() { setTimeout(() => { const skeletonElements = document.querySelectorAll('.skeleton-loading'); skeletonElements.forEach((element, index) => { // Stagger the transition for a nice effect setTimeout(() => { element.classList.add('loaded'); // Remove skeleton-loading class after transition completes setTimeout(() => { element.classList.remove('skeleton-loading'); }, 300); }, index * 30); // 30ms stagger between elements }); console.log( '[Workspace] [SUCCESS] Skeleton to loaded transition completed' ); }, 500); // Wait a bit for data to settle } /** * Show data loading error */ showDataLoadingError() { const tableContent = document.getElementById('workspace-table-content'); if (tableContent) { tableContent.innerHTML = ` <div style="text-align: center; padding: 2rem;"> <h2 style="color: #d32f2f; margin-bottom: 1rem;">Error Loading Data</h2> <p style="color: #6c757d; margin-bottom: 1.5rem;">Unable to load workspace data. Please try again.</p> <button onclick="location.reload()" style="padding: 10px 20px; background: #022d54; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px;"> Retry </button> </div> `; } } /** * Show error message */ showError(error) { const container = document.getElementById(this.options.containerId); if (container) { container.innerHTML = ` <div style="background: #f8f9fa; display: flex; align-items: center; justify-content: center;"> <div style="text-align: center; padding: 2rem; background: white; border-radius: 8px; border: 1px solid #e0e0e0; max-width: 500px;"> <h3 style="color: #d32f2f; margin-bottom: 1rem;">Error Loading Workspace Management</h3> <p style="color: #6c757d; margin-bottom: 1.5rem;">${error.message || error}</p> <button onclick="location.reload()" style="padding: 10px 20px; background: #022d54; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px;"> Try Again </button> </div> </div> `; } } /** * Cleanup resources */ destroy() { this.initialized = false; } } // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = ManageWorkspacePage; } else if (typeof window !== 'undefined') { window.ManageWorkspacePage = ManageWorkspacePage; }