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:
169
admin-panel/admin/assets/css/admin-panel.css
Normal file
169
admin-panel/admin/assets/css/admin-panel.css
Normal 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%;
|
||||
}
|
||||
}
|
||||
276
admin-panel/admin/assets/js/admin-app.js
Normal file
276
admin-panel/admin/assets/js/admin-app.js
Normal 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();
|
||||
});
|
||||
Reference in New Issue
Block a user