/** * ToastManager - Sistema de Notificações Visuais * Gerencia notificações toast não-intrusivas para o usuário */ class ToastManager { constructor() { this.container = null; this.toasts = new Map(); this.config = { position: 'top-right', duration: 5000, maxToasts: 5, animationDuration: 300 }; this.init(); } init() { this.createContainer(); console.log('🍞 ToastManager inicializado'); } createContainer() { // Remover container existente se houver const existingContainer = document.querySelector('.toast-container'); if (existingContainer) { existingContainer.remove(); } // Criar novo container this.container = document.createElement('div'); this.container.className = `toast-container toast-${this.config.position}`; this.container.style.cssText = ` position: fixed; z-index: 10000; pointer-events: none; `; // Definir posição baseada na configuração this.setContainerPosition(); document.body.appendChild(this.container); } setContainerPosition() { if (!this.container) return; const positions = { 'top-right': { top: '20px', right: '20px', bottom: 'auto', left: 'auto' }, 'top-left': { top: '20px', left: '20px', bottom: 'auto', right: 'auto' }, 'top-center': { top: '20px', left: '50%', transform: 'translateX(-50%)', bottom: 'auto', right: 'auto' }, 'bottom-right': { bottom: '20px', right: '20px', top: 'auto', left: 'auto' }, 'bottom-left': { bottom: '20px', left: '20px', top: 'auto', right: 'auto' }, 'bottom-center': { bottom: '20px', left: '50%', transform: 'translateX(-50%)', top: 'auto', right: 'auto' } }; const pos = positions[this.config.position] || positions['top-right']; Object.assign(this.container.style, pos); } show(message, type = 'info', duration = null, options = {}) { const toast = this.createToast(message, type, options); const toastId = toast.dataset.toastId; // Adicionar ao container this.container.appendChild(toast); // Armazenar referência this.toasts.set(toastId, toast); // Limitar número de toasts this.limitToasts(); // Animar entrada requestAnimationFrame(() => { toast.classList.add('show'); }); // Configurar tempo de remoção const autoHideDuration = duration !== null ? duration : this.config.duration; if (autoHideDuration > 0) { setTimeout(() => { this.hide(toastId); }, autoHideDuration); } return toastId; } createToast(message, type, options) { const toast = document.createElement('div'); const toastId = 'toast_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); toast.dataset.toastId = toastId; toast.className = `toast toast-${type}`; toast.style.cssText = ` background: var(--toast-bg, #333); color: var(--toast-color, #fff); padding: 16px 20px; margin-bottom: 10px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; line-height: 1.4; max-width: 400px; min-width: 300px; opacity: 0; transform: translateX(100%); transition: all ${this.config.animationDuration}ms ease; pointer-events: auto; position: relative; overflow: hidden; `; // Definir cores baseadas no tipo const colors = { success: { bg: '#22c55e', color: '#fff' }, error: { bg: '#ef4444', color: '#fff' }, warning: { bg: '#f59e0b', color: '#fff' }, info: { bg: '#3b82f6', color: '#fff' } }; const colorConfig = colors[type] || colors.info; toast.style.setProperty('--toast-bg', colorConfig.bg); toast.style.setProperty('--toast-color', colorConfig.color); // Adicionar ícone baseado no tipo const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; const icon = options.icon || icons[type] || icons.info; toast.innerHTML = `