Files
analiticaNucleo/nuxt4-app/app/composables/useSidebarState.ts
josedario87 ca72746138
Some checks failed
build-and-deploy / build-and-deploy (push) Has been cancelled
Fix: Eliminar race condition en sincronización del icono de toggle
Problema:
- El icono del toggle mostraba comportamiento inconsistente entre páginas
- Algunas páginas cargaban con icono correcto (hamburguesa) y otras con "X"
- Race condition entre múltiples watchers compitiendo por el estado

Causa raíz detectada:
1. DashboardSidebar.vue:58 tiene watcher con {immediate:true} que sincroniza
   nuestro composable → contexto de DashboardGroup
2. DashboardSidebar.vue:60 tiene watcher que cierra sidebar en navegación
3. Nuestro composable TAMBIÉN tenía un watcher de navegación (duplicado)
4. Múltiples watchers registrándose cada vez que se llama useSidebarState()
5. Race condition: dependiendo del orden de montaje de componentes,
   el toggle podía leer el estado antes o después de la sincronización

Solución:
- Eliminar completamente el watcher de navegación de nuestro composable
- Dejar que DashboardSidebar de Nuxt UI maneje el cierre en navegación
- Nuestro composable solo mantiene el estado, no lógica de ciclo de vida
- Evita múltiples registros de watchers
- Elimina la race condition

Cambios:
- Removido watcher de route.fullPath (líneas 110-116)
- Removidas importaciones innecesarias (watch, useRoute)
- Actualizado comentario del header
- Simplificado: composable ahora es puramente state management

Resultado:
✓ Icono consistente en todas las páginas
✓ No más race conditions
✓ Menos watchers = mejor performance
✓ DashboardSidebar maneja toda la lógica de navegación

Referencias:
- app/composables/useSidebarState.ts:14
- app/composables/useSidebarState.ts:29
2025-10-30 11:28:11 -06:00

120 lines
2.9 KiB
TypeScript

/**
* Composable unificado para manejar el estado de la sidebar
*
* Centraliza todo el estado relacionado con la sidebar para evitar
* inconsistencias entre múltiples refs y watchers en cascada.
*
* Características:
* - Estado persistente en cookies
* - Sincronización automática entre open/collapsed
* - Manejo de responsive (mobile vs desktop)
* - DashboardSidebar de Nuxt UI maneja el cierre en navegación
*/
import { ref, computed } from 'vue'
// Storage key para las cookies
const STORAGE_KEY = 'analytics-dashboard-sidebar'
// Tipos
interface SidebarState {
open: boolean
collapsed: boolean
size: number
}
// Estado global singleton
const sidebarState = ref<SidebarState | null>(null)
export function useSidebarState() {
// Detectar si estamos en mobile (debe hacerse antes de leer el estado)
const isMobile = computed(() => {
if (import.meta.server) return false
return window.innerWidth < 1024 // lg breakpoint
})
// Inicializar estado solo una vez (singleton)
if (!sidebarState.value) {
// Leer de cookie si existe
const savedState = useCookie<SidebarState>(STORAGE_KEY, {
default: () => ({
// En desktop, open=false porque no hay overlay (la sidebar siempre está visible)
// En mobile, open=false significa que el overlay está cerrado (comportamiento correcto)
open: false,
collapsed: false,
size: 28 // defaultSize
})
})
sidebarState.value = savedState.value
}
// Referencias reactivas al estado
const open = computed({
get: () => sidebarState.value?.open ?? false,
set: (value: boolean) => {
if (sidebarState.value) {
sidebarState.value.open = value
// Persistir en cookie
const cookie = useCookie<SidebarState>(STORAGE_KEY)
cookie.value = sidebarState.value
}
}
})
const collapsed = computed({
get: () => sidebarState.value?.collapsed ?? false,
set: (value: boolean) => {
if (sidebarState.value) {
sidebarState.value.collapsed = value
// Persistir en cookie
const cookie = useCookie<SidebarState>(STORAGE_KEY)
cookie.value = sidebarState.value
}
}
})
const size = computed({
get: () => sidebarState.value?.size ?? 28,
set: (value: number) => {
if (sidebarState.value) {
sidebarState.value.size = value
// Persistir en cookie
const cookie = useCookie<SidebarState>(STORAGE_KEY)
cookie.value = sidebarState.value
}
}
})
// Funciones de control
function toggle() {
open.value = !open.value
}
function toggleCollapse() {
collapsed.value = !collapsed.value
}
function setOpen(value: boolean) {
open.value = value
}
function setCollapsed(value: boolean) {
collapsed.value = value
}
return {
// Estado
open,
collapsed,
size,
isMobile,
// Acciones
toggle,
toggleCollapse,
setOpen,
setCollapsed
}
}