Files
roi-theme/admin/assets/js/admin-app.js
FrankZamora de5fff4f5c Fase 1: Estructura Base y DI Container - Clean Architecture
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>
2025-11-17 13:48:24 -06:00

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();
});