/** * MusicTeach Feedback Bundle * Sistema padronizado para exibição de mensagens de feedback, toast e alertas * * @version 1.0.0 */ /** * Classe principal para gerenciar todos os tipos de feedback do aplicativo */ class MusicTeachFeedback { constructor() { this.initializeStyles(); this.toastTimeout = null; this.feedbackTimeout = null; } /** * Inicializa os estilos CSS necessários */ initializeStyles() { // Verificar se os estilos já foram adicionados if (document.getElementById("musicteach-feedback-styles")) return; // Criar elemento de estilo const styleElement = document.createElement("style"); styleElement.id = "musicteach-feedback-styles"; // Definir estilos CSS para todos os componentes de feedback styleElement.textContent = ` /* Toast - Aparece no topo da página */ .musicteach-toast { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 9999; min-width: 250px; max-width: 320px; padding: 12px 16px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); font-family: "Poppins", sans-serif; font-size: 0.9rem; display: flex; align-items: center; animation: fadeInDown 0.3s ease forwards; } .musicteach-toast.hiding { animation: fadeOutUp 0.3s ease forwards; } .musicteach-toast i { margin-right: 10px; font-size: 1.1rem; } .musicteach-toast.success { background-color: #d4edda; color: #155724; border-left: 4px solid #28a745; } .musicteach-toast.danger { background-color: #f8d7da; color: #721c24; border-left: 4px solid #dc3545; } .musicteach-toast.warning { background-color: #fff3cd; color: #856404; border-left: 4px solid #ffc107; } .musicteach-toast.info { background-color: #d1ecf1; color: #0c5460; border-left: 4px solid #17a2b8; } /* Feedback Alert - Aparece no canto inferior direito */ .musicteach-feedback-alert { position: fixed; bottom: 110px !important; right: 30px; left: auto; top: auto; max-width: 320px; min-width: 180px; padding: 12px 20px; border-radius: 8px; background: #fff; color: var(--text-dark, #333); box-shadow: 0 2px 12px rgba(98, 59, 229, 0.1); border: 1px solid var(--border-light, #dee2e6); font-size: 0.95rem; opacity: 0.97; z-index: 9999; display: none; transition: opacity 0.3s, transform 0.3s; pointer-events: none; font-family: "Poppins", sans-serif; } .musicteach-feedback-alert.success { border-color: #19875433; background: #eafaf1; color: #198754; border-left: 4px solid #198754; font-weight: 500; } .musicteach-feedback-alert.danger { border-color: #dc354533; background: #fff0f2; color: #dc3545; border-left: 4px solid #dc3545; font-weight: 500; } .musicteach-feedback-alert.warning { border-color: #ffc10733; background: #fffbe6; color: #b8860b; border-left: 4px solid #ffc107; font-weight: 500; } .musicteach-feedback-alert.info { border-color: #0dcaf033; background: #f0f8ff; color: #0d6efd; border-left: 4px solid #0d6efd; } .musicteach-feedback-alert.show { display: block; opacity: 1; transform: translateY(0); pointer-events: auto; } /* Animações compartilhadas */ @keyframes fadeInDown { from { opacity: 0; transform: translate(-50%, -20px); } to { opacity: 1; transform: translate(-50%, 0); } } @keyframes fadeOutUp { from { opacity: 1; transform: translate(-50%, 0); } to { opacity: 0; transform: translate(-50%, -20px); } } /* Animação de pulso para alertas de erro/aviso */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.02); } 100% { transform: scale(1); } } .pulse-animation { animation: pulse 2s infinite; } /* Modal de Confirmação */ .musicteach-confirm-modal .modal-content { border-radius: 1rem; border: none; } .musicteach-confirm-modal .modal-header { border-bottom: 1px solid rgba(0, 0, 0, 0.05); background-color: rgba(98, 59, 229, 0.05); } .musicteach-confirm-modal .modal-title { font-weight: 600; font-size: 1.1rem; } .musicteach-confirm-modal .modal-footer { border-top: 1px solid rgba(0, 0, 0, 0.05); } /* Responsividade Feedback para Tablet/Desktop (768px+) */ @media (min-width: 768px) { /* Feedback toast responsivo */ .app-toast { min-width: 320px !important; max-width: 450px !important; padding: 16px 20px !important; font-size: 1rem !important; border-radius: 12px !important; bottom: 30px !important; right: 30px !important; } .app-toast i { font-size: 1.2rem; margin-right: 12px; } /* Melhor animação para desktop */ .app-toast { animation: slideInFromRight 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); } .app-toast.hiding { animation: slideOutToRight 0.3s cubic-bezier(0.55, 0.055, 0.675, 0.19); } @keyframes slideInFromRight { 0% { transform: translateX(100%); opacity: 0; } 100% { transform: translateX(0); opacity: 1; } } @keyframes slideOutToRight { 0% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(100%); opacity: 0; } } } `; // Adicionar ao DOM document.head.appendChild(styleElement); } /** * Exibe um toast no topo da página * @param {string} message - Mensagem a ser exibida * @param {string} type - Tipo do toast: 'success', 'danger', 'warning', 'info' * @param {number} duration - Duração em ms (padrão: 3000ms) */ showToast(message, type = "info", duration = 3000) { // Remover qualquer toast existente this.clearToasts(); // Definir ícone baseado no tipo let icon; switch (type) { case "success": icon = "fa-check-circle"; break; case "danger": icon = "fa-times-circle"; break; case "warning": icon = "fa-exclamation-circle"; break; default: // 'info' icon = "fa-info-circle"; } // Criar elemento de toast const toast = document.createElement("div"); toast.className = `musicteach-toast ${type}`; toast.setAttribute("role", "alert"); // Adicionar conteúdo toast.innerHTML = ` ${message} `; // Adicionar ao DOM document.body.appendChild(toast); // Remover após a duração especificada this.toastTimeout = setTimeout(() => { toast.classList.add("hiding"); setTimeout(() => { if (document.body.contains(toast)) { document.body.removeChild(toast); } }, 300); }, duration); } /** * Limpar todos os toasts ativos */ clearToasts() { // Limpar timeout existente if (this.toastTimeout) { clearTimeout(this.toastTimeout); } // Remover todos os toasts existentes const existingToasts = document.querySelectorAll(".musicteach-toast"); existingToasts.forEach((toast) => { toast.classList.add("hiding"); setTimeout(() => { if (document.body.contains(toast)) { document.body.removeChild(toast); } }, 300); }); } /** * Exibe um alert de feedback no canto inferior direito * @param {string} message - Mensagem a ser exibida * @param {string} type - Tipo do feedback: 'success', 'danger', 'warning', 'info' * @param {number} duration - Duração em ms (padrão: 3000ms) */ showFeedback(message, type = "info", duration = 3000) { // Verificar se já existe um elemento de feedback let feedbackAlert = document.getElementById("musicteach-feedback-alert"); // Se não existir, criar um novo if (!feedbackAlert) { feedbackAlert = document.createElement("div"); feedbackAlert.id = "musicteach-feedback-alert"; feedbackAlert.className = "musicteach-feedback-alert"; feedbackAlert.setAttribute("role", "alert"); document.body.appendChild(feedbackAlert); } // Limpar qualquer timeout anterior if (this.feedbackTimeout) { clearTimeout(this.feedbackTimeout); } // Atualizar classes e conteúdo feedbackAlert.className = `musicteach-feedback-alert ${type}`; // Permitir HTML na mensagem feedbackAlert.innerHTML = message; // Adicionar animação para tipos de alerta específicos if (type === "danger" || type === "warning") { feedbackAlert.classList.add("pulse-animation"); } // Mostrar o alerta feedbackAlert.classList.add("show"); // Configurar o timer para ocultar this.feedbackTimeout = setTimeout(() => { // Animação de saída suave feedbackAlert.style.opacity = "0"; feedbackAlert.style.transform = "translateY(10px)"; // Remover completamente após a animação setTimeout(() => { feedbackAlert.classList.remove("show"); feedbackAlert.style.display = "none"; feedbackAlert.style.opacity = "1"; feedbackAlert.style.transform = "translateY(0)"; // Remover animação de pulso, se presente if (feedbackAlert.classList.contains("pulse-animation")) { feedbackAlert.classList.remove("pulse-animation"); } }, 300); }, duration); } /** * Esconde o feedback atual */ hideFeedback() { const feedbackAlert = document.getElementById("musicteach-feedback-alert"); if (feedbackAlert) { feedbackAlert.classList.remove("show"); // Limpar qualquer timeout pendente if (this.feedbackTimeout) { clearTimeout(this.feedbackTimeout); } } } /** * Criar e exibir um modal de confirmação customizado * @param {Object} options - Opções de configuração * @param {string} options.title - Título do modal * @param {string} options.message - Mensagem do modal * @param {string} options.confirmText - Texto do botão de confirmação * @param {string} options.cancelText - Texto do botão de cancelamento * @param {string} options.confirmType - Tipo do botão de confirmação ('primary', 'danger', etc) * @param {Function} options.onConfirm - Callback quando confirmado * @param {Function} options.onCancel - Callback quando cancelado * @returns {Object} - Instância do modal */ showConfirmModal(options) { const defaults = { title: "Confirmar Ação", message: "Tem certeza que deseja continuar?", confirmText: "Confirmar", cancelText: "Cancelar", confirmType: "primary", confirmIcon: "fa-check", cancelIcon: "fa-xmark", onConfirm: () => {}, onCancel: () => {}, }; // Mesclar opções const settings = { ...defaults, ...options }; // ID único para este modal const modalId = "musicteach-confirm-modal-" + Date.now(); // Criar o elemento do modal const modalElement = document.createElement("div"); modalElement.className = "modal fade musicteach-confirm-modal"; modalElement.id = modalId; modalElement.setAttribute("tabindex", "-1"); modalElement.setAttribute("aria-labelledby", `${modalId}-label`); modalElement.setAttribute("aria-hidden", "true"); // HTML do modal modalElement.innerHTML = `