/** * JavaScript para el Dashboard del Panel de Administración ROI Theme * Vanilla JavaScript - No frameworks */ (function() { 'use strict'; /** * Inicializa el dashboard cuando el DOM está listo */ document.addEventListener('DOMContentLoaded', function() { initializeTabs(); initializeFormValidation(); initializeButtons(); initializeColorPickers(); }); /** * Inicializa el sistema de tabs */ function initializeTabs() { const tabs = document.querySelectorAll('.nav-tab'); tabs.forEach(function(tab) { tab.addEventListener('click', function(e) { // Prevenir comportamiento por defecto si es necesario // (En este caso dejamos que funcione la navegación normal) }); }); } /** * Inicializa validación de formularios */ function initializeFormValidation() { const forms = document.querySelectorAll('.roi-component-config form'); forms.forEach(function(form) { form.addEventListener('submit', function(e) { if (!validateForm(form)) { e.preventDefault(); showError('Por favor, corrige los errores en el formulario.'); } }); }); } /** * Valida un formulario * * @param {HTMLFormElement} form Formulario a validar * @returns {boolean} True si es válido */ function validateForm(form) { let isValid = true; const requiredFields = form.querySelectorAll('[required]'); requiredFields.forEach(function(field) { if (!field.value.trim()) { field.classList.add('error'); isValid = false; } else { field.classList.remove('error'); } }); return isValid; } /** * Muestra un mensaje de error * * @param {string} message Mensaje a mostrar */ function showError(message) { const notice = document.createElement('div'); notice.className = 'notice notice-error is-dismissible'; notice.innerHTML = '

' + escapeHtml(message) + '

'; const h1 = document.querySelector('.roi-admin-dashboard h1'); if (h1 && h1.nextElementSibling) { h1.nextElementSibling.after(notice); } } /** * Escapa HTML para prevenir XSS * * @param {string} text Texto a escapar * @returns {string} Texto escapado */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Inicializa los botones del panel */ function initializeButtons() { // Botón Guardar Cambios const saveButton = document.getElementById('saveSettings'); if (saveButton) { saveButton.addEventListener('click', handleSaveSettings); } // Botón Cancelar const cancelButton = document.getElementById('cancelChanges'); if (cancelButton) { cancelButton.addEventListener('click', handleCancelChanges); } // Botones Restaurar valores por defecto (dinámico para todos los componentes) const resetButtons = document.querySelectorAll('.btn-reset-defaults[data-component]'); resetButtons.forEach(function(button) { button.addEventListener('click', function(e) { const componentId = this.getAttribute('data-component'); handleResetDefaults(e, componentId, this); }); }); } /** * Guarda los cambios del formulario */ function handleSaveSettings(e) { e.preventDefault(); // Obtener el tab activo const activeTab = document.querySelector('.tab-pane.active'); if (!activeTab) { showNotice('error', 'No hay ningún componente seleccionado.'); return; } // Obtener el ID del componente desde el tab const componentId = activeTab.id.replace('Tab', ''); // Recopilar todos los campos del formulario activo const formData = collectFormData(activeTab); // Mostrar loading en el botón const saveButton = document.getElementById('saveSettings'); const originalText = saveButton.innerHTML; saveButton.disabled = true; saveButton.innerHTML = ' Guardando...'; // Enviar por AJAX fetch(ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'roi_save_component_settings', nonce: roiAdminDashboard.nonce, component: componentId, settings: JSON.stringify(formData) }) }) .then(response => response.json()) .then(data => { if (data.success) { showNotice('success', data.data.message || 'Cambios guardados correctamente.'); } else { showNotice('error', data.data.message || 'Error al guardar los cambios.'); } }) .catch(error => { console.error('Error:', error); showNotice('error', 'Error de conexión al guardar los cambios.'); }) .finally(() => { saveButton.disabled = false; saveButton.innerHTML = originalText; }); } /** * Cancela los cambios y recarga la página */ function handleCancelChanges(e) { e.preventDefault(); showConfirmModal( 'Cancelar cambios', '¿Descartar todos los cambios no guardados?', function() { location.reload(); } ); } /** * Restaura los valores por defecto de un componente * * @param {Event} e Evento del click * @param {string} componentId ID del componente a resetear * @param {HTMLElement} resetButton Elemento del botón que disparó el evento */ function handleResetDefaults(e, componentId, resetButton) { e.preventDefault(); showConfirmModal( 'Restaurar valores por defecto', '¿Restaurar todos los valores a los valores por defecto? Esta acción no se puede deshacer.', function() { // Mostrar loading en el botón const originalText = resetButton.innerHTML; resetButton.disabled = true; resetButton.innerHTML = ' Restaurando...'; // Enviar por AJAX fetch(ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'roi_reset_component_defaults', nonce: roiAdminDashboard.nonce, component: componentId }) }) .then(response => response.json()) .then(data => { if (data.success) { showNotice('success', data.data.message || 'Valores restaurados correctamente.'); setTimeout(() => location.reload(), 1500); } else { showNotice('error', data.data.message || 'Error al restaurar los valores.'); } }) .catch(error => { console.error('Error:', error); showNotice('error', 'Error de conexión al restaurar los valores.'); }) .finally(() => { resetButton.disabled = false; resetButton.innerHTML = originalText; }); } ); } /** * Recopila los datos del formulario del tab activo */ function collectFormData(container) { const formData = {}; // Inputs de texto, textarea, select, color, number, email, password const textInputs = container.querySelectorAll('input[type="text"], input[type="url"], input[type="color"], input[type="number"], input[type="email"], input[type="password"], textarea, select'); textInputs.forEach(input => { if (input.id) { formData[input.id] = input.value; } }); // Checkboxes (switches) const checkboxes = container.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(checkbox => { if (checkbox.id) { formData[checkbox.id] = checkbox.checked; } }); return formData; } /** * Muestra un toast de Bootstrap */ function showNotice(type, message) { // Mapear tipos const typeMap = { 'success': { bg: 'success', icon: 'bi-check-circle-fill', text: 'Éxito' }, 'error': { bg: 'danger', icon: 'bi-x-circle-fill', text: 'Error' }, 'warning': { bg: 'warning', icon: 'bi-exclamation-triangle-fill', text: 'Advertencia' }, 'info': { bg: 'info', icon: 'bi-info-circle-fill', text: 'Información' } }; const config = typeMap[type] || typeMap['info']; // Crear container de toasts si no existe let toastContainer = document.getElementById('roiToastContainer'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'roiToastContainer'; toastContainer.className = 'toast-container position-fixed start-50 translate-middle-x'; toastContainer.style.top = '60px'; toastContainer.style.zIndex = '999999'; document.body.appendChild(toastContainer); } // Crear toast const toastId = 'toast-' + Date.now(); const toastHTML = ` `; toastContainer.insertAdjacentHTML('beforeend', toastHTML); // Mostrar toast const toastElement = document.getElementById(toastId); const toast = new bootstrap.Toast(toastElement, { autohide: true, delay: 5000 }); toast.show(); // Eliminar del DOM después de ocultarse toastElement.addEventListener('hidden.bs.toast', function() { toastElement.remove(); }); } /** * Muestra un modal de confirmación de Bootstrap */ function showConfirmModal(title, message, onConfirm) { // Crear modal si no existe let modal = document.getElementById('roiConfirmModal'); if (!modal) { const modalHTML = ` `; document.body.insertAdjacentHTML('beforeend', modalHTML); modal = document.getElementById('roiConfirmModal'); } // Actualizar contenido document.getElementById('roiConfirmModalTitle').textContent = title; document.getElementById('roiConfirmModalBody').textContent = message; // Configurar callback const confirmButton = document.getElementById('roiConfirmModalConfirm'); const newConfirmButton = confirmButton.cloneNode(true); confirmButton.parentNode.replaceChild(newConfirmButton, confirmButton); newConfirmButton.addEventListener('click', function() { const bsModal = bootstrap.Modal.getInstance(modal); bsModal.hide(); if (typeof onConfirm === 'function') { onConfirm(); } }); // Mostrar modal const bsModal = new bootstrap.Modal(modal); bsModal.show(); } /** * Inicializa los color pickers para mostrar el valor HEX */ function initializeColorPickers() { const colorPickers = document.querySelectorAll('input[type="color"]'); colorPickers.forEach(picker => { // Elemento donde se muestra el valor HEX const valueDisplay = document.getElementById(picker.id + 'Value'); if (valueDisplay) { picker.addEventListener('input', function() { valueDisplay.textContent = this.value.toUpperCase(); }); } }); } })();