Files
analiticaNucleo/nuxt4-app/app/components/app/AppSidebar.vue
josedario87 b9c780d667
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 49s
Refactor: Eliminar páginas de metadatos y rawExplorer
Remueve las páginas en modo mantenimiento que no se van a utilizar:

Páginas eliminadas:
- /metadatos - Visualización de estructura de tablas
- /rawExplorer - Exploración de datos en formato raw

Cambios en sidebar:
- Removidos enlaces de "Metadatos" y "Explorador de datos raw"
- Sidebar ahora solo muestra páginas activas y funcionales

Páginas que permanecen:
- Panorama Facturador (funcional)
- Informe Ingresos (funcional)
- Explorador de datos (funcional)
- Comparativa Cosechas (en mantenimiento, se restaurará próximamente)
- Metabase Debug (herramienta de desarrollo)

Simplifica la navegación mostrando solo funcionalidades relevantes.
2025-10-30 17:23:34 -06:00

328 lines
12 KiB
Vue

<template>
<UDashboardSidebar
v-model:open="open"
v-model:collapsed="collapsed"
collapsible
resizable
:default-size="28"
:min-size="20"
:max-size="38"
:toggle="{ color: 'neutral', variant: 'ghost', class: 'rounded-full hover:bg-[var(--brand-primary)]/10 text-[var(--brand-text-muted)] hover:text-[var(--brand-primary)]' }"
:ui="{
root: 'bg-[var(--brand-surface)] border-r border-[var(--brand-border)]'
}"
>
<template #header="{ collapsed: isCollapsed }">
<div class="flex items-center gap-2">
<img
v-if="!isCollapsed"
src="/logo.png"
alt="Analítica Núcleo"
class="h-8 w-8 rounded-full border border-[var(--brand-accent)]/40"
/>
<UIcon v-else name="i-lucide-activity" class="size-5 text-[var(--brand-accent)]" />
<span v-if="!isCollapsed" class="text-sm font-semibold text-[var(--brand-text)]">Analítica Núcleo</span>
</div>
</template>
<template #default="{ collapsed: isCollapsed }">
<UButton
:label="isCollapsed ? undefined : 'Buscar...'"
icon="i-lucide-search"
color="neutral"
variant="outline"
block
:square="isCollapsed"
class="mb-4 border-[var(--brand-border)] bg-[var(--brand-bg)]/50 hover:bg-[var(--brand-primary)]/10 hover:border-[var(--brand-primary)]/30 text-[var(--brand-text-muted)] hover:text-[var(--brand-primary)]"
>
<template v-if="!isCollapsed" #trailing>
<div class="flex items-center gap-0.5 ms-auto text-[var(--brand-text-muted)]">
<UKbd value="⌘" variant="subtle" class="bg-[var(--brand-surface)] border-[var(--brand-border)] text-[var(--brand-text-muted)]" />
<UKbd value="K" variant="subtle" class="bg-[var(--brand-surface)] border-[var(--brand-border)] text-[var(--brand-text-muted)]" />
</div>
</template>
</UButton>
<UNavigationMenu
:collapsed="isCollapsed"
:items="navigationPrimary"
orientation="vertical"
class="gap-1"
:ui="{
item: {
base: 'text-[var(--brand-text-muted)] hover:text-[var(--brand-primary)] hover:bg-[var(--brand-primary)]/10',
active: 'bg-[var(--brand-primary)]/15 text-[var(--brand-primary)] border-l-2 border-[var(--brand-primary)] font-medium',
inactive: 'border-l-2 border-transparent'
}
}"
/>
</template>
<template #footer="{ collapsed: isCollapsed }">
<div v-if="isAuthenticated" class="space-y-3">
<!-- User Profile Section -->
<div
v-if="!isCollapsed"
class="p-0 space-y-2"
>
<!-- User Info Card -->
<div class="px-3 py-2.5 bg-gradient-to-br from-[var(--brand-surface)] to-transparent rounded-lg border border-[var(--brand-border)]/60">
<div class="flex items-center gap-3">
<div class="relative flex-shrink-0">
<UAvatar
v-bind="userAvatar"
size="md"
:ui="{
wrapper: 'ring-2 ring-[var(--brand-primary)]/50 shadow-sm'
}"
/>
<span
class="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-[var(--brand-accent)] border-2 border-[var(--brand-surface)] rounded-full shadow-sm"
title="En línea"
/>
</div>
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm text-[var(--brand-text)] truncate leading-tight">
{{ user.name || user.username }}
</p>
<p class="text-xs text-[var(--brand-text-muted)] truncate mt-0.5">
{{ user.email }}
</p>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="space-y-1">
<UButton
to="https://inicio.nucleoriofrio.com"
color="neutral"
variant="ghost"
size="sm"
block
class="justify-start gap-2.5 hover:bg-[var(--brand-primary)]/10 transition-all duration-300 hover:shadow-sm"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-2.5 py-2' }
}"
>
<template #leading>
<div class="w-7 h-7 rounded-md bg-[var(--brand-surface)] flex items-center justify-center flex-shrink-0 relative overflow-hidden shadow-sm border border-[var(--brand-border)]">
<img
src="/perfil-icon.png"
alt="Perfil"
class="w-4 h-4 absolute inset-0 m-auto object-cover rounded-sm opacity-90"
@error="handlePerfilIconError"
/>
<UIcon name="i-lucide-home" class="size-3.5 text-[var(--brand-primary)] opacity-0" />
</div>
</template>
<span class="text-sm font-medium text-[var(--brand-text)]">Inicio</span>
</UButton>
<UButton
to="/settings"
color="neutral"
variant="ghost"
size="sm"
block
class="justify-start gap-2.5 hover:bg-[var(--brand-primary)]/10 transition-all duration-200"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-2.5 py-2' }
}"
>
<template #leading>
<div class="w-7 h-7 rounded-md bg-[var(--brand-surface)] flex items-center justify-center flex-shrink-0">
<UIcon name="i-lucide-settings" class="size-3.5 text-[var(--brand-text-muted)]" />
</div>
</template>
<span class="text-sm font-medium text-[var(--brand-text)]">Configuración</span>
</UButton>
<UButton
to="/notifications"
color="neutral"
variant="ghost"
size="sm"
block
class="justify-start gap-2.5 hover:bg-[var(--brand-primary)]/10 transition-all duration-200"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-2.5 py-2' }
}"
>
<template #leading>
<div class="w-7 h-7 rounded-md bg-[var(--brand-surface)] flex items-center justify-center relative flex-shrink-0">
<UIcon name="i-lucide-bell" class="size-3.5 text-[var(--brand-primary)]" />
<span class="absolute -top-0.5 -right-0.5 w-2 h-2 bg-[var(--brand-accent)] rounded-full ring-1 ring-[var(--brand-surface)]" />
</div>
</template>
<span class="text-sm font-medium text-[var(--brand-text)]">Notificaciones</span>
<template #trailing>
<UBadge color="neutral" variant="solid" size="xs" class="ml-auto bg-[var(--brand-accent)] text-[var(--brand-bg)]">3</UBadge>
</template>
</UButton>
<div class="pt-2 mt-1.5 border-t border-[var(--brand-border)]">
<UButton
color="neutral"
variant="ghost"
size="sm"
block
class="justify-start gap-2.5 hover:bg-[var(--brand-primary)]/10 transition-all duration-200 group"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-2.5 py-2' }
}"
@click="logout"
>
<template #leading>
<div class="w-7 h-7 rounded-md bg-[var(--brand-surface)] flex items-center justify-center flex-shrink-0 group-hover:bg-[var(--brand-border)] transition-colors">
<UIcon name="i-lucide-log-out" class="size-3.5 text-[var(--brand-primary)]" />
</div>
</template>
<span class="text-sm font-medium text-[var(--brand-primary)]">Cerrar sesión</span>
</UButton>
</div>
</div>
</div>
<!-- Collapsed View -->
<div v-else class="flex flex-col items-center gap-3">
<div class="relative group">
<UButton
color="neutral"
variant="ghost"
square
class="relative hover:bg-[var(--brand-primary)]/10 transition-all duration-200"
:ui="{ rounded: 'rounded-lg' }"
>
<div class="relative">
<UAvatar
v-bind="userAvatar"
size="sm"
:ui="{
wrapper: 'ring-2 ring-[var(--brand-primary)]/40 group-hover:ring-[var(--brand-primary)]/60 transition-all duration-200 shadow-sm'
}"
/>
<span
class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 bg-[var(--brand-accent)] border-2 border-[var(--brand-surface)] rounded-full shadow-sm"
/>
</div>
</UButton>
</div>
<div class="w-full h-px bg-[var(--brand-border)]" />
<UButton
@click="logout"
color="neutral"
variant="ghost"
square
class="hover:bg-[var(--brand-primary)]/10 transition-all duration-200"
:ui="{ rounded: 'rounded-lg' }"
>
<UIcon name="i-lucide-log-out" class="size-4 text-[var(--brand-primary)]" />
</UButton>
</div>
</div>
</template>
</UDashboardSidebar>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import type { NavigationMenuItem } from '@nuxt/ui'
const route = useRoute()
// Usar el composable unificado para el estado de la sidebar
const sidebarState = useSidebarState()
// Exponer como models para compatibilidad con UDashboardSidebar
const open = computed({
get: () => sidebarState.open.value,
set: (value: boolean) => sidebarState.setOpen(value)
})
const collapsed = computed({
get: () => sidebarState.collapsed.value,
set: (value: boolean) => sidebarState.setCollapsed(value)
})
// Manejo del fallback de iconos para el botón Inicio
const perfilIconFallbackIndex = ref(0)
const perfilIconSources = [
'/perfil-icon.png',
'https://inicio.nucleoriofrio.com/apple-touch-icon.png',
'https://inicio.nucleoriofrio.com/favicon.ico'
]
const handlePerfilIconError = (event: Event) => {
const img = event.target as HTMLImageElement
perfilIconFallbackIndex.value++
if (perfilIconFallbackIndex.value < perfilIconSources.length) {
img.src = perfilIconSources[perfilIconFallbackIndex.value]
} else {
// Si todos los iconos fallan, mostrar el icono de casa
img.style.display = 'none'
const icon = img.nextElementSibling as HTMLElement
if (icon) {
icon.style.opacity = '1'
}
}
}
const navigationPrimary = computed<NavigationMenuItem[]>(() => [
{
label: 'Inicio',
icon: 'i-lucide-home',
to: '/',
active: route.path === '/'
},
{
label: 'Panorama Facturador',
icon: 'i-lucide-bar-chart-3',
to: '/panorama',
active: route.path === '/panorama'
},
{
label: 'Informe Ingresos',
icon: 'i-lucide-file-bar-chart',
to: '/informe-ingresos',
active: route.path === '/informe-ingresos'
},
{
label: 'Comparativa Cosechas',
icon: 'i-lucide-calendar-range',
to: '/comparativa-cosechas',
active: route.path === '/comparativa-cosechas',
badge: { label: 'Mantenimiento', color: 'amber' }
},
{
label: 'Explorador de datos',
icon: 'i-lucide-table',
to: '/explorer',
active: route.path === '/explorer'
},
{
label: 'Metabase Debug',
icon: 'i-lucide-bug',
to: '/metabase-debug',
active: route.path === '/metabase-debug'
}
])
const { user, isAuthenticated, logout } = useAuthentik()
// Computed para el avatar del usuario
const userAvatar = computed(() => ({
src: user.value?.avatar || `https://ui-avatars.com/api/?name=${encodeURIComponent(user.value?.name || user.value?.username || 'User')}&background=c08040&color=1b1209&bold=true&format=svg`,
alt: user.value?.name || user.value?.username || 'User'
}))
</script>