From cebf8d663d386333c0011f7815ea445d60950686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Consultor=C3=ADa=20Alcaraz=20Salazar?= Date: Fri, 18 Sep 2026 03:23:44 +0000 Subject: [PATCH] Permisos: marketing (export completo, agenda/citas, cumpleanos, visitas, WACRM), gerencia gestiona usuarios y roles, recepcion edita expedientes, cosmetologas ven agenda, fix del sidebar que no dejaba seleccionar Leads --- frontend/src/components/ClasicoLayout.tsx | 47 ++++- frontend/src/components/HomeNestLayout.tsx | 4 +- frontend/src/components/SkeenLayout.tsx | 37 +++- frontend/src/components/UsuariosPanel.tsx | 8 +- frontend/src/lib/auth.tsx | 16 +- frontend/src/pages/Agenda.tsx | 8 +- frontend/src/pages/Configuracion.tsx | 18 +- frontend/src/pages/Pacientes.tsx | 28 ++- frontend/src/services/odoo.ts | 8 +- .../skeen_whatsapp/controllers/auth.py | 21 +++ .../skeen_whatsapp/controllers/frontend.py | 170 ++++++++++++++++-- .../skeen_whatsapp/models/config_audit.py | 1 + odoo-addons/skeen_whatsapp/roles.py | 35 +++- 13 files changed, 354 insertions(+), 47 deletions(-) diff --git a/frontend/src/components/ClasicoLayout.tsx b/frontend/src/components/ClasicoLayout.tsx index a6ac59a..cb39aee 100644 --- a/frontend/src/components/ClasicoLayout.tsx +++ b/frontend/src/components/ClasicoLayout.tsx @@ -1,5 +1,5 @@ -import type { FC, ReactNode } from 'react'; -import { useEffect, useMemo, useState } from 'react'; +import type { FC, MutableRefObject, ReactNode } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { Link, useLocation, useNavigate } from 'react-router-dom'; import { LayoutDashboard, @@ -256,13 +256,47 @@ export const ClasicoLayout: FC = ({ children, title: _title, subtit const toggleGroup = (label: string) => setOpenGroups((prev) => (prev.includes(label) ? prev.filter((l) => l !== label) : [...prev, label])); - const SidebarContent = () => ( + // El menú se renderiza como función (no como componente) a propósito: como + // componente definido dentro del layout, cada render crea un tipo nuevo y + // React remonta el subárbol entero, perdiendo la posición de scroll (los + // últimos items, p. ej. Leads WACRM, quedaban inalcanzables). + const navDesktopRef = useRef(null); + const navMobileRef = useRef(null); + + // 1) El grupo que contiene la ruta activa siempre queda abierto + useEffect(() => { + const current = `${location.pathname}${location.search}`; + const abiertos = menuTree + .filter((e) => e.children?.some((c) => { + const base = c.href.split('?')[0]; + return current === c.href || (current.startsWith(base) && (!c.href.includes('?') || current.includes(c.href.split('?')[1]))); + })) + .map((e) => e.label); + if (!abiertos.length) return; + setOpenGroups((prev) => { + const faltan = abiertos.filter((l) => !prev.includes(l)); + return faltan.length ? [...prev, ...faltan] : prev; + }); + }, [location.pathname, location.search]); + + // 2) Con el grupo ya abierto (re-render), deja visible el item activo. + // Va en un efecto aparte a propósito: en el mismo tick el item todavía no + // está en el DOM y no habría nada que desplazar. + useEffect(() => { + for (const nav of [navDesktopRef.current, navMobileRef.current]) { + if (!nav || nav.scrollHeight <= nav.clientHeight) continue; + const el = nav.querySelector('[data-active="1"]'); + if (el) el.scrollIntoView({ block: 'nearest' }); + } + }, [location.pathname, location.search, openGroups, visibleLeaves]); + + const renderSidebar = ({ navRef = null }: { navRef?: MutableRefObject | null } = {}) => ( <> {/* Sucursal, como en el sistema legacy */}

Rosarito

-