All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 49s
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.
328 lines
12 KiB
Vue
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>
|