fix: sidebar — logo fijo en header y colapso de módulos funcional
Logo: - Carga doc_logo_base64 y empresa_nombre desde lab_config - Header muestra logo (filtro invert para fondo oscuro) o ícono fallback - Nombre del laboratorio siempre visible debajo, nunca cambia por página - Ícono de configuración reposicionado en esquina superior derecha Colapso de módulos: - Bug: transición CSS se disparaba durante init causando estado inconsistente - Fix: inyecta <style> temporal con transition:none antes de aplicar estados, lo remueve en el siguiente frame para que las animaciones funcionen luego Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
86cfad6e68
commit
6b6389c583
@@ -26,23 +26,33 @@ if (!class_exists('ModuleRegistry', false)) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Brand color desde DB (si no fue inyectado ya por Layout.php) ─────────────
|
||||
// ── Brand color + logo desde DB ──────────────────────────────────────────────
|
||||
if (!defined('_SIDEBAR_BRAND_INJECTED')) {
|
||||
define('_SIDEBAR_BRAND_INJECTED', true);
|
||||
$_sb_brand = '#1565c0';
|
||||
$_sb_brand_dark = '#0d47a1';
|
||||
$_sb_logo = '';
|
||||
$_sb_lab_nombre = '';
|
||||
try {
|
||||
if (class_exists('Database', false)) {
|
||||
$_sb_row = Database::getInstance()->fetch("SELECT valor FROM lab_config WHERE clave = 'doc_color'");
|
||||
if ($_sb_row && preg_match('/^#[0-9a-fA-F]{3,8}$/', $_sb_row['valor'])) {
|
||||
$_sb_brand = $_sb_row['valor'];
|
||||
$_sb_hex = ltrim($_sb_brand, '#');
|
||||
$_sb_brand_dark = sprintf('#%02x%02x%02x',
|
||||
max(0, hexdec(substr($_sb_hex, 0, 2)) - 40),
|
||||
max(0, hexdec(substr($_sb_hex, 2, 2)) - 40),
|
||||
max(0, hexdec(substr($_sb_hex, 4, 2)) - 40)
|
||||
);
|
||||
$__cfgs = Database::getInstance()->fetchAll(
|
||||
"SELECT clave, valor FROM lab_config WHERE clave IN ('doc_color','doc_logo_base64','empresa_nombre')"
|
||||
);
|
||||
foreach ($__cfgs as $__r) {
|
||||
if ($__r['clave'] === 'doc_color' && preg_match('/^#[0-9a-fA-F]{3,8}$/', $__r['valor'])) {
|
||||
$_sb_brand = $__r['valor'];
|
||||
} elseif ($__r['clave'] === 'doc_logo_base64' && !empty($__r['valor'])) {
|
||||
$_sb_logo = $__r['valor'];
|
||||
} elseif ($__r['clave'] === 'empresa_nombre') {
|
||||
$_sb_lab_nombre = $__r['valor'];
|
||||
}
|
||||
}
|
||||
$_sb_hex = ltrim($_sb_brand, '#');
|
||||
$_sb_brand_dark = sprintf('#%02x%02x%02x',
|
||||
max(0, hexdec(substr($_sb_hex, 0, 2)) - 40),
|
||||
max(0, hexdec(substr($_sb_hex, 2, 2)) - 40),
|
||||
max(0, hexdec(substr($_sb_hex, 4, 2)) - 40)
|
||||
);
|
||||
}
|
||||
} catch (\Throwable $_) {}
|
||||
echo "<style>:root{--brand:{$_sb_brand};--brand-dark:{$_sb_brand_dark}}</style>\n";
|
||||
@@ -75,9 +85,7 @@ $_isActive = function (string $route) use ($_current_script): string {
|
||||
return $routeBase === $_current_script ? ' active' : '';
|
||||
};
|
||||
|
||||
// Título del sidebar: prioridad al nombre definido por el módulo, luego el genérico
|
||||
$_sidebar_title = $SIDEBAR_TITLE ?? $GLOBALS['_LAYOUT_TITLE'] ?? 'Panel ERP';
|
||||
$_sidebar_icon = $SIDEBAR_ICON ?? $GLOBALS['_LAYOUT_ICON'] ?? 'fas fa-th-large';
|
||||
$_sb_display_name = $_sb_lab_nombre ?: 'Panel ERP';
|
||||
|
||||
// Nombre del usuario para el pie del sidebar
|
||||
$_user_name = $_SESSION['admin_user']['full_name']
|
||||
@@ -103,10 +111,15 @@ $_base = defined('APP_URL') ? rtrim(APP_URL, '/') : '';
|
||||
<nav class="sidebar" id="erp-sidebar">
|
||||
|
||||
<div class="sidebar-header">
|
||||
<h4 style="flex:1;min-width:0">
|
||||
<i class="<?= htmlspecialchars($_sidebar_icon, ENT_QUOTES, 'UTF-8') ?>"></i>
|
||||
<?= htmlspecialchars($_sidebar_title, ENT_QUOTES, 'UTF-8') ?>
|
||||
</h4>
|
||||
<?php if ($_sb_logo): ?>
|
||||
<img src="<?= htmlspecialchars($_sb_logo, ENT_QUOTES, 'UTF-8') ?>"
|
||||
alt="Logo" class="sb-logo-img">
|
||||
<?php else: ?>
|
||||
<div class="sb-logo-fallback">
|
||||
<i class="fas fa-flask"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="sb-brand-name"><?= htmlspecialchars($_sb_display_name, ENT_QUOTES, 'UTF-8') ?></div>
|
||||
<a href="<?= $_base ?>/lab_configuracion.php" class="sb-config-btn" title="Configuración">
|
||||
<i class="fas fa-gear"></i>
|
||||
</a>
|
||||
@@ -223,15 +236,50 @@ if (class_exists('ModuleRegistry', false)) {
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
/* ── Botón config en header ─────────────────────────── */
|
||||
.sidebar-header { display:flex; align-items:center; gap:10px; }
|
||||
/* ── Header con logo ────────────────────────────────── */
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 18px 16px 14px;
|
||||
position: relative;
|
||||
}
|
||||
.sb-logo-img {
|
||||
max-width: 120px;
|
||||
max-height: 60px;
|
||||
object-fit: contain;
|
||||
filter: brightness(0) invert(1);
|
||||
opacity: .92;
|
||||
}
|
||||
.sb-logo-fallback {
|
||||
width: 52px; height: 52px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255,255,255,.18);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.5rem; color: #fff;
|
||||
}
|
||||
.sb-brand-name {
|
||||
font-size: .72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .04em;
|
||||
color: rgba(255,255,255,.80);
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* config en esquina superior derecha */
|
||||
.sb-config-btn {
|
||||
color: rgba(255,255,255,.65);
|
||||
font-size: 1rem;
|
||||
padding: 4px 6px;
|
||||
position: absolute;
|
||||
top: 10px; right: 10px;
|
||||
color: rgba(255,255,255,.55);
|
||||
font-size: .9rem;
|
||||
padding: 5px 7px;
|
||||
border-radius: 6px;
|
||||
transition: color .15s, background .15s;
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
.sb-config-btn:hover { color:#fff; background:rgba(255,255,255,.15); }
|
||||
@@ -387,23 +435,32 @@ if (class_exists('ModuleRegistry', false)) {
|
||||
});
|
||||
};
|
||||
|
||||
// Inicializar al cargar
|
||||
// Inicializar al cargar — sin animación para evitar flash
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var collapsed = getCollapsed();
|
||||
// Deshabilitar transición durante init
|
||||
var style = document.createElement('style');
|
||||
style.id = '_sb_notrans';
|
||||
style.textContent = '.sb-group { transition: none !important; }';
|
||||
document.head.appendChild(style);
|
||||
|
||||
document.querySelectorAll('.sb-group').forEach(function(group) {
|
||||
var catId = group.id;
|
||||
var section = document.querySelector('[data-cat="' + catId + '"]');
|
||||
var catId = group.id;
|
||||
var section = document.querySelector('[data-cat="' + catId + '"]');
|
||||
if (!section) return;
|
||||
|
||||
var hasActive = !!group.querySelector('.nav-link.active');
|
||||
var hasActive = !!group.querySelector('.nav-link.active');
|
||||
var isCollapsed = !hasActive && collapsed.indexOf(catId) !== -1;
|
||||
|
||||
// Medir altura real antes de colapsar
|
||||
// Altura real (funciona incluso con overflow:hidden)
|
||||
group.style.maxHeight = group.scrollHeight + 'px';
|
||||
group.style.opacity = '1';
|
||||
|
||||
applyState(group, section, isCollapsed, false);
|
||||
});
|
||||
|
||||
// Reactivar transición en el próximo frame
|
||||
requestAnimationFrame(function() {
|
||||
var el = document.getElementById('_sb_notrans');
|
||||
if (el) el.remove();
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user