/** * 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 = `
Loading...
`; loader.id = `${containerId}-loader`; container.appendChild(loader); } /** * Hide loading indicator */ hideLoading(containerId) { const loader = document.getElementById(`${containerId}-loader`); if (loader) { loader.remove(); } } /** * Update WebSocket status indicator */ updateWSStatus(status) { const statusEl = document.getElementById('ws-status'); const statusTextEl = document.getElementById('ws-status-text'); if (!statusEl || !statusTextEl) return; statusEl.className = 'ws-status'; switch (status) { case 'connected': statusEl.classList.add('ws-connected'); statusTextEl.textContent = 'Connected'; break; case 'connecting': statusEl.classList.add('ws-connecting'); statusTextEl.textContent = 'Connecting...'; break; case 'disconnected': statusEl.classList.add('ws-disconnected'); statusTextEl.textContent = 'Disconnected'; break; } } /** * Format time duration */ formatDuration(minutes) { if (minutes < 60) { return `${minutes}min`; } const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours}h ${mins}min`; } /** * Create patient card element */ createPatientCard(entry) { const card = document.createElement('div'); card.className = 'patient-card mb-2'; card.dataset.entryId = entry.id; card.innerHTML = `
${entry.queue_position}
${entry.patient_name}
Wait: ${this.formatDuration(entry.wait_time_minutes)}
Priority: ${entry.priority_score.toFixed(1)}
`; return card; } /** * Get priority color based on score */ getPriorityColor(score) { if (score >= 8) return 'danger'; if (score >= 5) return 'warning'; return 'success'; } /** * Scroll to element smoothly */ scrollToElement(elementId) { const element = document.getElementById(elementId); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } /** * Enable/disable sound notifications */ toggleSound(enabled) { this.soundEnabled = enabled; localStorage.setItem('queueSoundEnabled', enabled); } /** * Get sound preference from storage */ getSoundPreference() { const stored = localStorage.getItem('queueSoundEnabled'); return stored !== null ? stored === 'true' : this.soundEnabled; } } // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = QueueUIManager; }