Actualizar frontend/src/components/Sidebar.jsx

This commit is contained in:
2026-09-02 11:14:43 -05:00
parent df11d8a237
commit 25438e017c

View File

@@ -1,11 +1,17 @@
// src/components/Sidebar.jsx // src/components/Sidebar.jsx
import { useState } from "react";
import { useAuth } from "../lib/auth"; import { useAuth } from "../lib/auth";
const MENU = [ const MENU = [
{ id: "ocupabilidad", ico: "📊", label: "Ocupabilidad" }, { id: "ocupabilidad", ico: "📊", label: "Ocupabilidad" },
{ id: "ventas", ico: "💰", label: "Ventas" }, { id: "ventas", ico: "💰", label: "Ventas" },
{ id: "comisiones", ico: "🧮", label: "Comisiones" }, { id: "comisiones", ico: "🧮", label: "Comisiones" },
{ id: "cobranza", ico: "📋", label: "Cobranza" }, // Grupo COBRANZA con submenú (CARTERA = cobranza, RETIROS = retiros)
{ id: "grupo-cobranza", ico: "📋", label: "Cobranza", grupo: true,
hijos: [
{ id: "cobranza", ico: "📁", label: "Cartera" },
{ id: "retiros", ico: "🚪", label: "Retiros" },
] },
{ id: "rentabilidad", ico: "📈", label: "Rentabilidad" }, { id: "rentabilidad", ico: "📈", label: "Rentabilidad" },
{ id: "asesores", ico: "👥", label: "Asesores" }, { id: "asesores", ico: "👥", label: "Asesores" },
{ id: "usuarios", ico: "🔐", label: "Usuarios" }, { id: "usuarios", ico: "🔐", label: "Usuarios" },
@@ -13,25 +19,94 @@ const MENU = [
export default function Sidebar({ active, onChange }) { export default function Sidebar({ active, onChange }) {
const { perfil, permisos, logout } = useAuth(); const { perfil, permisos, logout } = useAuth();
const visibles = MENU.filter((m) => permisos.includes(m.id)); const [colapsado, setColapsado] = useState(false);
// Un grupo se muestra si el usuario tiene permiso a al menos uno de sus hijos.
const puede = (m) => m.grupo
? m.hijos.some((h) => permisos.includes(h.id))
: permisos.includes(m.id);
const visibles = MENU.filter(puede);
// Submenú abierto: por defecto abierto si la vista activa es uno de sus hijos.
const [abiertos, setAbiertos] = useState(() => {
const ini = {};
MENU.forEach((m) => { if (m.grupo && m.hijos.some((h) => h.id === active)) ini[m.id] = true; });
return ini;
});
const toggle = (id) => setAbiertos((p) => ({ ...p, [id]: !p[id] }));
return ( return (
<aside className="sidebar"> <aside className={`sidebar ${colapsado ? "collapsed" : ""}`}>
<div className="sidebar-logo">Escuela <span>Refrigeración</span></div> <div className="sidebar-logo" style={{ display: "flex", alignItems: "center",
justifyContent: colapsado ? "center" : "space-between", gap: 8 }}>
<span className="sidebar-logo-text">Escuela <span>Refrigeración</span></span>
<button className="sidebar-toggle" onClick={() => setColapsado((v) => !v)}
title={colapsado ? "Expandir menú" : "Colapsar menú"}>☰</button>
</div>
<nav className="nav" style={{ flex: 1 }}> <nav className="nav" style={{ flex: 1 }}>
{visibles.map((m) => ( {visibles.map((m) => {
<button if (!m.grupo) {
key={m.id} return (
className={`nav-item ${active === m.id ? "active" : ""}`} <button
onClick={() => onChange(m.id)} key={m.id}
> className={`nav-item ${active === m.id ? "active" : ""}`}
<span className="ico">{m.ico}</span> {m.label} onClick={() => onChange(m.id)}
</button> title={colapsado ? m.label : undefined}
))} >
<span className="ico">{m.ico}</span> <span className="nav-label">{m.label}</span>
</button>
);
}
// Grupo con submenú desplegable
const hijosVis = m.hijos.filter((h) => permisos.includes(h.id));
const abierto = !!abiertos[m.id];
const hijoActivo = hijosVis.some((h) => h.id === active);
return (
<div key={m.id}>
<button
className={`nav-item ${hijoActivo && !abierto ? "active" : ""}`}
onClick={() => { if (colapsado) { setColapsado(false); setAbiertos((p)=>({ ...p, [m.id]: true })); } else { toggle(m.id); } }}
title={colapsado ? m.label : undefined}
style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}
>
<span><span className="ico">{m.ico}</span> <span className="nav-label">{m.label}</span></span>
<span className="nav-caret" style={{ fontSize: 10, transition: "transform .25s ease",
transform: abierto ? "rotate(180deg)" : "rotate(0deg)" }}>▼</span>
</button>
{/* Submenú con transición suave de despliegue */}
<div style={{
display: "grid",
gridTemplateRows: abierto ? "1fr" : "0fr",
transition: "grid-template-rows .28s ease",
}}>
<div style={{ overflow: "hidden" }}>
<div style={{
paddingTop: abierto ? 2 : 0,
opacity: abierto ? 1 : 0,
transform: abierto ? "translateY(0)" : "translateY(-6px)",
transition: "opacity .25s ease, transform .25s ease",
}}>
{hijosVis.map((h) => (
<button
key={h.id}
className={`nav-item ${active === h.id ? "active" : ""}`}
onClick={() => onChange(h.id)}
tabIndex={abierto ? 0 : -1}
style={{ paddingLeft: 34, fontSize: 13.5 }}
>
<span className="ico">{h.ico}</span> <span className="nav-label">{h.label}</span>
</button>
))}
</div>
</div>
</div>
</div>
);
})}
</nav> </nav>
<div style={{ padding: 14, borderTop: "1px solid rgba(255,255,255,0.08)" }}> <div className="sidebar-footer" style={{ padding: 14, borderTop: "1px solid rgba(255,255,255,0.08)" }}>
<div style={{ fontSize: 12, color: "#94a3b8", marginBottom: 2 }}> <div style={{ fontSize: 12, color: "#94a3b8", marginBottom: 2 }}>
{perfil?.nombre || "Usuario"} {perfil?.nombre || "Usuario"}
</div> </div>
@@ -51,4 +126,4 @@ export default function Sidebar({ active, onChange }) {
</div> </div>
</aside> </aside>
); );
} }