Feat: Implementar Top Bar configurable - Issue #143

Implementación completa del componente Top Bar con 15 campos configurables desde el admin panel, siguiendo el algoritmo universal v2.0.

## Cambios Realizados

### Backend (PHP)
- Agregados defaults del Top Bar a class-settings-manager.php
- Implementada validación completa en class-validator.php (15 campos)
- Implementada sanitización con sanitize_text_field, esc_url_raw y sanitize_hex_color
- Modificado header.php con código 100% configurable usando wp_parse_args()

### Frontend (Admin Panel)
- Creado tab HTML completo con 3 secciones: Activación, Contenido y Estilos
- Implementado JavaScript para renderizado y recolección de datos
- 15 campos configurables: enabled, visibility, icon, content, link, custom styles

### Infraestructura
- Creado admin-panel/init.php para carga del módulo
- Creada class-admin-menu.php con enqueue de Bootstrap 5 y assets
- Creada estructura base CSS y JavaScript del admin
- Ya cargado en functions.php línea 276

## Características
- Responsive: Control independiente mobile/desktop
- Estilos personalizables: 4 colores + tamaño de fuente
- Validación robusta: Límites de caracteres, URLs, colores hex
- Defaults inteligentes: Valores seguros si no hay configuración

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2025-11-09 15:13:55 -06:00
parent bbc6ed2c98
commit 465b879135
8 changed files with 1136 additions and 5 deletions

View File

@@ -0,0 +1,169 @@
/**
* Admin Panel Styles
*
* Estilos base para el panel de administración
*
* @package Apus_Theme
* @since 2.0.0
*/
/* ========================================
Container
======================================== */
.apus-admin-panel {
max-width: 1400px;
margin: 20px auto;
}
/* ========================================
Header
======================================== */
.apus-admin-panel h1 {
margin-bottom: 10px;
}
.apus-admin-panel .description {
color: #666;
margin-bottom: 20px;
}
/* ========================================
Tabs
======================================== */
.nav-tabs {
border-bottom: 2px solid #dee2e6;
}
.nav-tabs .nav-link {
color: #666;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
}
.nav-tabs .nav-link:hover {
color: #0073aa;
border-bottom-color: #0073aa;
}
.nav-tabs .nav-link.active {
color: #0073aa;
font-weight: 600;
border-bottom-color: #0073aa;
background-color: transparent;
}
/* ========================================
Tab Content
======================================== */
.tab-content {
background: #fff;
padding: 20px;
border: 1px solid #ddd;
border-radius: 4px;
}
.tab-pane h3 {
margin-top: 0;
margin-bottom: 15px;
font-size: 18px;
}
.tab-pane h4 {
margin-top: 25px;
margin-bottom: 10px;
font-size: 16px;
color: #333;
}
/* ========================================
Form Sections
======================================== */
.form-section {
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.form-section:last-child {
border-bottom: none;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 5px;
color: #333;
}
.form-group input[type="text"],
.form-group input[type="url"],
.form-group input[type="email"],
.form-group input[type="number"],
.form-group select,
.form-group textarea {
max-width: 600px;
}
.form-group .form-text {
margin-top: 5px;
font-size: 13px;
}
.form-group .form-text code {
background: #f5f5f5;
padding: 2px 5px;
border-radius: 3px;
font-size: 12px;
}
/* ========================================
Action Buttons
======================================== */
.admin-actions {
padding: 20px;
background: #f9f9f9;
border-top: 1px solid #ddd;
border-radius: 4px;
}
.admin-actions .button-primary {
font-size: 14px;
padding: 8px 20px;
height: auto;
}
.admin-actions .button-primary i {
vertical-align: middle;
}
/* ========================================
Responsive
======================================== */
@media (max-width: 782px) {
.apus-admin-panel {
margin: 10px;
}
.tab-content {
padding: 15px;
}
.form-group input[type="text"],
.form-group input[type="url"],
.form-group input[type="email"],
.form-group select,
.form-group textarea {
max-width: 100%;
}
}

View File

@@ -0,0 +1,276 @@
/**
* Admin Panel Application
*
* Gestión de configuraciones de componentes del tema
*
* @package Apus_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
document.getElementById('saveSettings').addEventListener('click', () => {
this.saveSettings();
});
// Detectar cambios en formularios
document.querySelectorAll('input, select, textarea').forEach(input => {
input.addEventListener('change', () => {
this.STATE.hasChanges = true;
document.getElementById('saveSettings').disabled = false;
});
});
// 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: apusAdminData.ajaxUrl,
data: new URLSearchParams({
action: 'apus_get_settings',
nonce: apusAdminData.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();
const response = await axios({
method: 'POST',
url: apusAdminData.ajaxUrl,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({
action: 'apus_save_settings',
nonce: apusAdminData.nonce,
...formData
})
});
if (response.data.success) {
this.STATE.hasChanges = false;
document.getElementById('saveSettings').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í
*/
collectFormData() {
return {
components: {
top_bar: {
enabled: document.getElementById('topBarEnabled').checked,
show_on_mobile: document.getElementById('topBarShowOnMobile').checked,
show_on_desktop: document.getElementById('topBarShowOnDesktop').checked,
icon_class: document.getElementById('topBarIconClass').value.trim(),
show_icon: document.getElementById('topBarShowIcon').checked,
highlight_text: document.getElementById('topBarHighlightText').value.trim(),
message_text: document.getElementById('topBarMessageText').value.trim(),
link_text: document.getElementById('topBarLinkText').value.trim(),
link_url: document.getElementById('topBarLinkUrl').value.trim(),
link_target: document.getElementById('topBarLinkTarget').value,
show_link: document.getElementById('topBarShowLink').checked,
custom_styles: {
background_color: this.getColorValue('topBarBgColor', ''),
text_color: this.getColorValue('topBarTextColor', ''),
highlight_color: this.getColorValue('topBarHighlightColor', ''),
link_hover_color: this.getColorValue('topBarLinkHoverColor', ''),
font_size: document.getElementById('topBarFontSize').value
}
}
// Navbar - Pendiente
// Hero - Pendiente
// Footer - Pendiente
}
};
},
/**
* Renderizar todos los componentes
*/
renderAllComponents() {
const components = this.STATE.settings.components || {};
// Top Bar
if (components.top_bar) {
this.renderTopBar(components.top_bar);
}
// Navbar - Pendiente
// Hero - Pendiente
// Footer - Pendiente
},
/**
* Renderizar Top Bar
*/
renderTopBar(topBar) {
document.getElementById('topBarEnabled').checked = topBar.enabled !== undefined ? topBar.enabled : true;
document.getElementById('topBarShowOnMobile').checked = topBar.show_on_mobile !== undefined ? topBar.show_on_mobile : true;
document.getElementById('topBarShowOnDesktop').checked = topBar.show_on_desktop !== undefined ? topBar.show_on_desktop : true;
document.getElementById('topBarIconClass').value = topBar.icon_class || 'bi bi-megaphone-fill';
document.getElementById('topBarShowIcon').checked = topBar.show_icon !== undefined ? topBar.show_icon : true;
document.getElementById('topBarHighlightText').value = topBar.highlight_text || 'Nuevo:';
document.getElementById('topBarMessageText').value = topBar.message_text || 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.';
document.getElementById('topBarLinkText').value = topBar.link_text || 'Ver Catálogo';
document.getElementById('topBarLinkUrl').value = topBar.link_url || '/catalogo';
document.getElementById('topBarLinkTarget').value = topBar.link_target || '_self';
document.getElementById('topBarShowLink').checked = topBar.show_link !== undefined ? topBar.show_link : true;
// Estilos personalizados
if (topBar.custom_styles) {
if (topBar.custom_styles.background_color) {
document.getElementById('topBarBgColor').value = topBar.custom_styles.background_color;
}
if (topBar.custom_styles.text_color) {
document.getElementById('topBarTextColor').value = topBar.custom_styles.text_color;
}
if (topBar.custom_styles.highlight_color) {
document.getElementById('topBarHighlightColor').value = topBar.custom_styles.highlight_color;
}
if (topBar.custom_styles.link_hover_color) {
document.getElementById('topBarLinkHoverColor').value = topBar.custom_styles.link_hover_color;
}
document.getElementById('topBarFontSize').value = topBar.custom_styles.font_size || 'normal';
}
},
/**
* Utilidad: Obtener valor de color con fallback
*/
getColorValue(inputId, defaultValue) {
const input = document.getElementById(inputId);
const value = input ? input.value.trim() : '';
return value || defaultValue;
},
/**
* 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('.apus-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();
});