/** * Queue UI Manager * Handles UI updates and animations for queue management * Part of Phase 11: Advanced Queue Management */ class QueueUIManager { constructor(options = {}) { this.animationDuration = options.animationDuration || 300; this.soundEnabled = options.soundEnabled || false; this.sounds = { patientCalled: options.sounds?.patientCalled || null, positionChange: options.sounds?.positionChange || null, queueUpdate: options.sounds?.queueUpdate || null }; } /** * Update queue statistics display */ updateStatistics(stats) { this.updateElement('current-size', stats.current_size); this.updateElement('avg-wait-time', stats.average_wait_time); this.updateElement('load-factor', `${stats.load_factor.toFixed(2)}x`); // Update load bar this.updateLoadBar(stats); // Animate changes this.animateStatChange('current-size'); } /** * Update load bar visualization */ updateLoadBar(stats) { const loadBar = document.getElementById('load-bar'); if (!loadBar) return; const utilization = (stats.current_size / stats.max_size) * 100; loadBar.style.width = `${utilization}%`; // Update color based on utilization loadBar.className = 'load-bar'; if (utilization < 50) { loadBar.classList.add('load-normal'); } else if (utilization < 75) { loadBar.classList.add('load-moderate'); } else { loadBar.classList.add('load-high'); } } /** * Update queue list via HTMX */ refreshQueueList() { const container = document.getElementById('queue-list-container'); if (container && typeof htmx !== 'undefined') { htmx.trigger(container, 'refresh'); } } /** * Show patient called notification */ showPatientCalled(data) { const message = `${data.patient_name} has been called (Position ${data.position})`; // Show toast notification if (window.HospitalApp && window.HospitalApp.utils) { window.HospitalApp.utils.showToast(message, 'success'); } // Play sound if enabled if (this.soundEnabled && this.sounds.patientCalled) { this.playSound(this.sounds.patientCalled); } // Highlight the called patient this.highlightPatient(data.entry_id); } /** * Show position change notification */ showPositionChange(data) { const message = `Position changed for ${data.patient_name}: ${data.old_position} → ${data.new_position}`; // Show toast notification if (window.HospitalApp && window.HospitalApp.utils) { window.HospitalApp.utils.showToast(message, 'info'); } // Play sound if enabled if (this.soundEnabled && this.sounds.positionChange) { this.playSound(this.sounds.positionChange); } // Animate position change this.animatePositionChange(data.entry_id, data.old_position, data.new_position); } /** * Highlight a patient entry */ highlightPatient(entryId) { const row = document.querySelector(`[data-entry-id="${entryId}"]`); if (row) { row.classList.add('table-success'); setTimeout(() => { row.classList.remove('table-success'); }, 3000); } } /** * Animate position change */ animatePositionChange(entryId, oldPosition, newPosition) { const row = document.querySelector(`[data-entry-id="${entryId}"]`); if (!row) return; // Add animation class row.style.transition = `all ${this.animationDuration}ms ease`; if (newPosition < oldPosition) { // Moving up - flash green row.classList.add('bg-success', 'bg-opacity-25'); } else { // Moving down - flash warning row.classList.add('bg-warning', 'bg-opacity-25'); } setTimeout(() => { row.classList.remove('bg-success', 'bg-warning', 'bg-opacity-25'); }, this.animationDuration * 2); } /** * Animate statistic change */ animateStatChange(elementId) { const element = document.getElementById(elementId); if (!element) return; element.classList.add('pulse'); setTimeout(() => { element.classList.remove('pulse'); }, this.animationDuration); } /** * Update element text content */ updateElement(elementId, value) { const element = document.getElementById(elementId); if (element) { element.textContent = value; } } /** * Play sound notification */ playSound(soundUrl) { if (!soundUrl) return; try { const audio = new Audio(soundUrl); audio.volume = 0.5; audio.play().catch(error => { console.warn('Could not play sound:', error); }); } catch (error) { console.error('Error playing sound:', error); } } /** * Show loading indicator */ showLoading(containerId) { const container = document.getElementById(containerId); if (!container) return; const loader = document.createElement('div'); loader.className = 'text-center py-4'; loader.innerHTML = `