194 lines
6.2 KiB
Vue
194 lines
6.2 KiB
Vue
<script setup>
|
|
import { ref, watch, computed, onMounted } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { useUi } from '@/stores/useUi'
|
|
import { useRealtimeStore } from '@/stores/useRealtime'
|
|
|
|
const ui = useUi()
|
|
const realtime = useRealtimeStore()
|
|
|
|
// enlaces de la app
|
|
const links = [
|
|
{ to: '/', label: 'Chat', icon: '💬' },
|
|
{ to: '/empleados', label: 'Empleados', icon: '👥' },
|
|
{ to: '/tareas', label: 'Tareas', icon: '📋' },
|
|
{ to: '/planillas', label: 'Planillas', icon: '📂' },
|
|
{ to: '/asistencias', label: 'Asistencias', icon: '⏰' },
|
|
{ to: '/feed', label: 'Feed', icon: '📰' },
|
|
{ to: '/config', label: 'Config', icon: '⚙️' },
|
|
]
|
|
|
|
const route = useRoute()
|
|
const activePath = ref(route.path)
|
|
watch(route, v => {
|
|
activePath.value = v.path
|
|
const table = tableForPath(v.path)
|
|
if (table) realtime.clearBadgesForTable(table)
|
|
})
|
|
|
|
onMounted(() => {
|
|
const table = tableForPath(activePath.value)
|
|
if (table) realtime.clearBadgesForTable(table)
|
|
})
|
|
|
|
const accentColorForPath = (path) => {
|
|
if (path.startsWith('/empleados')) return ui.accentColorEmpleados
|
|
if (path.startsWith('/tareas')) return ui.accentColorTareas
|
|
if (path.startsWith('/planillas')) return ui.accentColorPlanillas
|
|
if (path.startsWith('/asistencias')) return ui.accentColorAsistencias
|
|
if (path.startsWith('/feed')) return ui.primaryColor
|
|
if (path.startsWith('/config')) return ui.accentColorConfiguracion
|
|
return ui.accentColorChat
|
|
}
|
|
|
|
const tableForPath = (path) => {
|
|
if (path.startsWith('/empleados')) return 'Cliente'
|
|
if (path.startsWith('/tareas')) return 'TareaRealizada'
|
|
if (path.startsWith('/planillas')) return 'Planilla'
|
|
if (path.startsWith('/asistencias')) return 'Asistencia'
|
|
return null
|
|
}
|
|
|
|
const hasBadge = (path, op) => {
|
|
const table = tableForPath(path)
|
|
return table && realtime.badges[table]?.[op]
|
|
}
|
|
|
|
const hasAnyBadge = (path) => {
|
|
const table = tableForPath(path)
|
|
if (!table) return false
|
|
const b = realtime.badges[table]
|
|
return b.INSERT || b.UPDATE || b.DELETE
|
|
}
|
|
|
|
// clases dinámicas p/ mostrar / ocultar barra
|
|
const sidebarClasses = computed(() => ui.sidebarOpen ? 'translate-x-0' : '-translate-x-full')
|
|
|
|
const handleLinkClick = (path) => {
|
|
// Close sidebar if desktopNavbarPersistent is false or if it's mobile view (width < 768px)
|
|
// Assuming 768px is the 'md' breakpoint.
|
|
if (!ui.desktopNavbarPersistent || window.innerWidth < 768) {
|
|
ui.closeSidebar()
|
|
}
|
|
// Clear badges for this module when the link is clicked
|
|
const table = tableForPath(path)
|
|
if (table) realtime.clearBadgesForTable(table)
|
|
|
|
// Otherwise, (desktopNavbarPersistent is true AND width >= 768px), do nothing to keep sidebar open.
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<!-- backdrop en mobile -->
|
|
<div v-if="ui.sidebarOpen" class="fixed inset-0 bg-black/40 md:hidden" @click="ui.closeSidebar" />
|
|
|
|
<!-- barra lateral -->
|
|
<aside
|
|
:class="['fixed left-0 top-0 md:top-14 h-screen w-60 flex flex-col select-none z-50 transform transition-transform duration-200 ease-in-out', sidebarClasses]"
|
|
style="background-color: transparent;">
|
|
|
|
<!-- encabezado dentro de sidebar -->
|
|
<div class="flex items-center justify-between px-4 py-4 md:px-5 md:py-4 md:border-none"
|
|
style="border-bottom-color: var(--secondary-color); border-bottom-width: 1px;">
|
|
<span class="text-lg font-semibold md:hidden" style="color: var(--primary-color);">Núcleo</span>
|
|
</div>
|
|
|
|
<!-- navegación -->
|
|
<nav class="flex-1 overflow-y-auto custom-scroll pr-1 pt-4 md:pt-0">
|
|
<ul class="px-2">
|
|
<li v-for="l in links" :key="l.to" class="mb-2 last:mb-0">
|
|
<RouterLink
|
|
:to="l.to"
|
|
class="nav-link flex items-center gap-3 w-full px-3 py-2 rounded-md font-medium transition group"
|
|
:class="[activePath.startsWith(l.to) ? 'active' : '', hasAnyBadge(l.to) ? 'notified' : '']"
|
|
:style="{ '--accent-color': accentColorForPath(l.to) }"
|
|
@click="handleLinkClick(l.to)"
|
|
>
|
|
<span class="text-lg" aria-hidden="true">{{ l.icon }}</span>
|
|
<span class="truncate flex-1">{{ l.label }}</span>
|
|
<span class="flex gap-1">
|
|
<span v-if="hasBadge(l.to, 'INSERT')" class="realtime-dot dot-insert"></span>
|
|
<span v-if="hasBadge(l.to, 'UPDATE')" class="realtime-dot dot-update"></span>
|
|
<span v-if="hasBadge(l.to, 'DELETE')" class="realtime-dot dot-delete"></span>
|
|
</span>
|
|
</RouterLink>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</aside>
|
|
</template>
|
|
|
|
<style scoped>
|
|
ul { list-style: none; padding-left: 0; }
|
|
|
|
.nav-link {
|
|
position: relative;
|
|
color: var(--text-color);
|
|
background-color: var(--background-color);
|
|
border: 1px solid var(--accent-color);
|
|
overflow: hidden;
|
|
border-radius: 0.375rem;
|
|
transition: background-color 0.3s ease, color 0.3s ease, transform 0.3s ease;
|
|
z-index: 0;
|
|
}
|
|
|
|
.nav-link::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(to right,
|
|
var(--accent-color) 0%,
|
|
var(--accent-color) 20%,
|
|
var(--background-color) 30%,
|
|
var(--background-color) 100%);
|
|
transform: translateX(-100%);
|
|
transition: transform 0.3s ease;
|
|
z-index: -1;
|
|
}
|
|
|
|
.nav-link:hover::before,
|
|
.nav-link.active::before {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.nav-link:hover {
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.close-btn {
|
|
border-color: var(--secondary-color);
|
|
color: var(--secondary-color);
|
|
}
|
|
|
|
.close-btn:hover {
|
|
background-color: var(--secondary-color);
|
|
color: var(--background-color);
|
|
}
|
|
|
|
.nav-link.active {
|
|
color: var(--text-color);
|
|
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px 0 rgba(0,0,0,0.06);
|
|
}
|
|
|
|
.nav-link.notified {
|
|
transform: translateX(10px);
|
|
}
|
|
|
|
/* Scrollbar styling using primary color */
|
|
.custom-scroll::-webkit-scrollbar { width: 8px; }
|
|
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
.custom-scroll::-webkit-scrollbar-thumb {
|
|
background-color: var(--primary-color);
|
|
opacity: 0.4;
|
|
border-radius: 4px;
|
|
}
|
|
.custom-scroll:hover::-webkit-scrollbar-thumb {
|
|
background-color: var(--primary-color);
|
|
opacity: 0.7;
|
|
}
|
|
.custom-scroll {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--primary-color) transparent; /* scrollbar-color: <thumb-color> <track-color> */
|
|
}
|
|
</style>
|