COMPLETADO: Fase 1 de la migración a Clean Architecture + POO ## Estructura de Carpetas - ✓ Estructura completa de 4 capas (Domain, Application, Infrastructure, Presentation) - ✓ Carpetas de Use Cases (SaveComponent, GetComponent, DeleteComponent, SyncSchema) - ✓ Estructura de tests (Unit, Integration, E2E) - ✓ Carpetas de schemas y templates ## Composer y Autoloading - ✓ PSR-4 autoloading configurado para ROITheme namespace - ✓ Autoloader optimizado regenerado ## DI Container - ✓ DIContainer implementado con patrón Singleton - ✓ Métodos set(), get(), has() para gestión de servicios - ✓ Getters específicos para ComponentRepository, ValidationService, CacheService - ✓ Placeholders que serán implementados en Fase 5 - ✓ Prevención de clonación y deserialización ## Interfaces - ✓ ComponentRepositoryInterface (Domain) - ✓ ValidationServiceInterface (Application) - ✓ CacheServiceInterface (Application) - ✓ Component entity placeholder (Domain) ## Bootstrap - ✓ functions.php actualizado con carga de Composer autoloader - ✓ Inicialización del DIContainer - ✓ Helper function roi_container() disponible globalmente ## Tests - ✓ 10 tests unitarios para DIContainer (100% cobertura) - ✓ Total: 13 tests unitarios, 28 assertions - ✓ Suite de tests pasando correctamente ## Validación - ✓ Script de validación automatizado (48/48 checks pasados) - ✓ 100% de validaciones exitosas La arquitectura base está lista para la Fase 2. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
220 lines
5.1 KiB
JavaScript
220 lines
5.1 KiB
JavaScript
/**
|
|
* Admin Panel Application
|
|
*
|
|
* Gestión de configuraciones de componentes del tema
|
|
*
|
|
* @package ROI_Theme
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
const AdminPanel = {
|
|
/**
|
|
* Estado de la aplicación
|
|
*/
|
|
STATE: {
|
|
settings: {},
|
|
hasChanges: false,
|
|
isLoading: false
|
|
},
|
|
|
|
/**
|
|
* Inicializar aplicación
|
|
*/
|
|
init() {
|
|
this.bindEvents();
|
|
this.loadSettings();
|
|
},
|
|
|
|
/**
|
|
* Vincular eventos
|
|
*/
|
|
bindEvents() {
|
|
// Botón guardar
|
|
const saveBtn = document.getElementById('saveSettings');
|
|
if (saveBtn) {
|
|
saveBtn.addEventListener('click', () => {
|
|
this.saveSettings();
|
|
});
|
|
}
|
|
|
|
// Detectar cambios en formularios
|
|
const enableSaveButton = () => {
|
|
this.STATE.hasChanges = true;
|
|
const btn = document.getElementById('saveSettings');
|
|
if (btn) btn.disabled = false;
|
|
};
|
|
|
|
document.querySelectorAll('input, select, textarea').forEach(input => {
|
|
// Evento 'input' se dispara mientras se escribe (tiempo real)
|
|
input.addEventListener('input', enableSaveButton);
|
|
// Evento 'change' se dispara cuando pierde foco (para select y checkboxes)
|
|
input.addEventListener('change', enableSaveButton);
|
|
});
|
|
|
|
// Tabs
|
|
const tabs = document.querySelectorAll('.nav-tabs .nav-link');
|
|
tabs.forEach(tab => {
|
|
tab.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
this.switchTab(tab);
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Cambiar tab
|
|
*/
|
|
switchTab(tab) {
|
|
// Remover active de todos
|
|
document.querySelectorAll('.nav-tabs .nav-link').forEach(t => {
|
|
t.classList.remove('active');
|
|
});
|
|
document.querySelectorAll('.tab-pane').forEach(pane => {
|
|
pane.classList.remove('show', 'active');
|
|
});
|
|
|
|
// Activar seleccionado
|
|
tab.classList.add('active');
|
|
const targetId = tab.getAttribute('data-bs-target').substring(1);
|
|
const targetPane = document.getElementById(targetId);
|
|
if (targetPane) {
|
|
targetPane.classList.add('show', 'active');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Cargar configuraciones desde servidor
|
|
*/
|
|
async loadSettings() {
|
|
this.STATE.isLoading = true;
|
|
this.showSpinner(true);
|
|
|
|
try {
|
|
const response = await axios({
|
|
method: 'POST',
|
|
url: roiAdminData.ajaxUrl,
|
|
data: new URLSearchParams({
|
|
action: 'roi_get_settings',
|
|
nonce: roiAdminData.nonce
|
|
})
|
|
});
|
|
|
|
if (response.data.success) {
|
|
this.STATE.settings = response.data.data;
|
|
this.renderAllComponents();
|
|
} else {
|
|
this.showNotice('Error al cargar configuraciones', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading settings:', error);
|
|
this.showNotice('Error de conexión', 'error');
|
|
} finally {
|
|
this.STATE.isLoading = false;
|
|
this.showSpinner(false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Guardar configuraciones al servidor
|
|
*/
|
|
async saveSettings() {
|
|
if (!this.STATE.hasChanges) {
|
|
this.showNotice('No hay cambios para guardar', 'info');
|
|
return;
|
|
}
|
|
|
|
this.showSpinner(true);
|
|
|
|
try {
|
|
const formData = this.collectFormData();
|
|
|
|
// Crear FormData para WordPress AJAX
|
|
const postData = new URLSearchParams();
|
|
postData.append('action', 'roi_save_settings');
|
|
postData.append('nonce', roiAdminData.nonce);
|
|
|
|
// Agregar components como JSON string
|
|
postData.append('components', JSON.stringify(formData.components));
|
|
|
|
const response = await axios({
|
|
method: 'POST',
|
|
url: roiAdminData.ajaxUrl,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
data: postData
|
|
});
|
|
|
|
if (response.data.success) {
|
|
this.STATE.hasChanges = false;
|
|
const btn = document.getElementById('saveSettings');
|
|
if (btn) btn.disabled = true;
|
|
this.showNotice('Configuración guardada correctamente', 'success');
|
|
} else {
|
|
this.showNotice(response.data.data.message || 'Error al guardar', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving settings:', error);
|
|
this.showNotice('Error de conexión', 'error');
|
|
} finally {
|
|
this.showSpinner(false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Recolectar datos del formulario
|
|
* Cada componente agregará su sección aquí cuando se implemente
|
|
*/
|
|
collectFormData() {
|
|
return {
|
|
components: {
|
|
// Los componentes se agregarán aquí cuando se ejecute el algoritmo
|
|
}
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Renderizar todos los componentes
|
|
*/
|
|
renderAllComponents() {
|
|
const components = this.STATE.settings.components || {};
|
|
|
|
// Los métodos render de componentes se llamarán aquí cuando se implementen
|
|
},
|
|
|
|
/**
|
|
* Utilidad: Mostrar spinner
|
|
*/
|
|
showSpinner(show) {
|
|
const spinner = document.querySelector('.spinner');
|
|
if (spinner) {
|
|
spinner.style.display = show ? 'inline-block' : 'none';
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Utilidad: Mostrar notificación
|
|
*/
|
|
showNotice(message, type = 'info') {
|
|
// WordPress admin notices
|
|
const noticeDiv = document.createElement('div');
|
|
noticeDiv.className = `notice notice-${type} is-dismissible`;
|
|
noticeDiv.innerHTML = `<p>${message}</p>`;
|
|
|
|
const container = document.querySelector('.roi-admin-panel');
|
|
if (container) {
|
|
container.insertBefore(noticeDiv, container.firstChild);
|
|
|
|
// Auto-dismiss después de 5 segundos
|
|
setTimeout(() => {
|
|
noticeDiv.remove();
|
|
}, 5000);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Inicializar cuando el DOM esté listo
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
AdminPanel.init();
|
|
});
|