feat(front) : render dynamic sidebar from /api/sidebar in default layout

This commit is contained in:
Matthieu
2026-06-19 15:32:23 +02:00
parent a620833550
commit 977e74f669
+48 -120
View File
@@ -38,131 +38,47 @@
</button>
</div>
<nav class="flex-1 overflow-hidden" :class="sidebarIsCollapsed ? 'px-1 pb-6' : 'px-4 pb-6'">
<SidebarLink
to="/"
icon="mdi:view-dashboard-outline"
label="Tableau de bord"
:collapsed="sidebarIsCollapsed"
:class="sidebarIsCollapsed ? 'mt-4' : 'border-t border-secondary-500 pt-6'"
@click="ui.closeMobileSidebar()"
/>
<!-- Section : Gestion de projet -->
<p v-if="!sidebarIsCollapsed" class="px-4 pt-5 pb-1 text-xs font-semibold uppercase tracking-wider text-neutral-400">
Gestion de projet
</p>
<div v-else class="mx-2 my-3 border-t border-secondary-500" />
<SidebarLink
to="/my-tasks"
icon="mdi:clipboard-check-outline"
label="Mes tâches"
:collapsed="sidebarIsCollapsed"
@click="ui.closeMobileSidebar()"
/>
<SidebarLink
to="/projects"
icon="mdi:folder-outline"
label="Projets"
:collapsed="sidebarIsCollapsed"
@click="ui.closeMobileSidebar()"
/>
<template v-if="currentProjectId">
<SidebarLink
:to="`/projects/${currentProjectId}`"
icon="mdi:view-column-outline"
label="Kanban"
:collapsed="sidebarIsCollapsed"
sub
exact
@click="ui.closeMobileSidebar()"
/>
<SidebarLink
:to="`/projects/${currentProjectId}/groups`"
icon="mdi:tag-multiple-outline"
label="Groupes"
:collapsed="sidebarIsCollapsed"
sub
@click="ui.closeMobileSidebar()"
/>
<SidebarLink
:to="`/projects/${currentProjectId}/archives`"
icon="mdi:archive-outline"
label="Archives"
:collapsed="sidebarIsCollapsed"
sub
@click="ui.closeMobileSidebar()"
/>
</template>
<SidebarLink
to="/time-tracking"
icon="mdi:calendar-edit-outline"
label="Suivi de temps"
:collapsed="sidebarIsCollapsed"
@click="ui.closeMobileSidebar()"
/>
<SidebarLink
v-if="isDocumentsVisible"
to="/documents"
icon="mdi:folder-network-outline"
:label="$t('sharedFiles.sidebar.title')"
:collapsed="sidebarIsCollapsed"
@click="ui.closeMobileSidebar()"
/>
<div v-if="isMailVisible" class="relative">
<SidebarLink
to="/mail"
icon="mdi:email-outline"
:label="$t('mail.sidebar.title')"
:collapsed="sidebarIsCollapsed"
@click="ui.closeMobileSidebar()"
/>
<span
v-if="mailStore.globalUnreadCount > 0"
class="pointer-events-none absolute right-3 top-1/2 flex h-5 min-w-5 -translate-y-1/2 items-center justify-center rounded-full bg-red-500 px-1 text-xs font-bold text-white"
:class="{ 'right-1 top-1 translate-y-0': sidebarIsCollapsed }"
:aria-label="`${mailStore.globalUnreadCount} messages non lus`"
>
{{ mailStore.globalUnreadCount > 99 ? '99+' : mailStore.globalUnreadCount }}
</span>
</div>
<!-- Section : Absences -->
<template v-if="isAbsenceSectionVisible">
<!-- Sections dynamiques (/api/sidebar) : navigation globale + sections gated par rôle -->
<template v-for="(section, sIndex) in translatedSections" :key="section.label">
<p v-if="!sidebarIsCollapsed" class="px-4 pt-5 pb-1 text-xs font-semibold uppercase tracking-wider text-neutral-400">
Absences
</p>
<div v-else class="mx-2 my-3 border-t border-secondary-500" />
</template>
<SidebarLink
v-if="isEmployee"
to="/absences"
icon="mdi:umbrella-beach-outline"
label="Mes absences"
:collapsed="sidebarIsCollapsed"
@click="ui.closeMobileSidebar()"
/>
<SidebarLink
v-if="isAdmin"
to="/team-absences"
icon="mdi:calendar-account-outline"
label="Absences équipe"
:collapsed="sidebarIsCollapsed"
@click="ui.closeMobileSidebar()"
/>
<!-- Section : Administration (admin only) -->
<template v-if="isAdmin">
<p v-if="!sidebarIsCollapsed" class="px-4 pt-5 pb-1 text-xs font-semibold uppercase tracking-wider text-neutral-400">
Administration
{{ section.label }}
</p>
<div v-else class="mx-2 my-3 border-t border-secondary-500" />
<SidebarLink
to="/admin"
icon="mdi:cog-outline"
label="Administration"
v-for="item in section.items"
:key="item.to"
:to="item.to"
:icon="item.icon"
:label="item.label"
:collapsed="sidebarIsCollapsed"
@click="ui.closeMobileSidebar()"
/>
<!-- Items conservés côté client, insérés après la 1re section (cf. décision 3) -->
<template v-if="sIndex === 0">
<!-- Contextuel projet -->
<template v-if="currentProjectId">
<SidebarLink :to="`/projects/${currentProjectId}`" icon="mdi:view-column-outline" label="Kanban" :collapsed="sidebarIsCollapsed" sub exact @click="ui.closeMobileSidebar()" />
<SidebarLink :to="`/projects/${currentProjectId}/groups`" icon="mdi:tag-multiple-outline" label="Groupes" :collapsed="sidebarIsCollapsed" sub @click="ui.closeMobileSidebar()" />
<SidebarLink :to="`/projects/${currentProjectId}/archives`" icon="mdi:archive-outline" label="Archives" :collapsed="sidebarIsCollapsed" sub @click="ui.closeMobileSidebar()" />
</template>
<!-- Feature-flag : Documents -->
<SidebarLink v-if="isDocumentsVisible" to="/documents" icon="mdi:folder-network-outline" :label="$t('sharedFiles.sidebar.title')" :collapsed="sidebarIsCollapsed" @click="ui.closeMobileSidebar()" />
<!-- Feature-flag : Mail + badge -->
<div v-if="isMailVisible" class="relative">
<SidebarLink to="/mail" icon="mdi:email-outline" :label="$t('mail.sidebar.title')" :collapsed="sidebarIsCollapsed" @click="ui.closeMobileSidebar()" />
<span
v-if="mailStore.globalUnreadCount > 0"
class="pointer-events-none absolute right-3 top-1/2 flex h-5 min-w-5 -translate-y-1/2 items-center justify-center rounded-full bg-red-500 px-1 text-xs font-bold text-white"
:class="{ 'right-1 top-1 translate-y-0': sidebarIsCollapsed }"
:aria-label="`${mailStore.globalUnreadCount} messages non lus`"
>
{{ mailStore.globalUnreadCount > 99 ? '99+' : mailStore.globalUnreadCount }}
</span>
</div>
<!-- User-flag : Mes absences (isEmployee non couvert par le gate rôle) -->
<SidebarLink v-if="isEmployee" to="/absences" icon="mdi:umbrella-beach-outline" label="Mes absences" :collapsed="sidebarIsCollapsed" @click="ui.closeMobileSidebar()" />
</template>
</template>
</nav>
@@ -220,10 +136,22 @@ const ui = useUiStore()
const mailStore = useMailStore()
const {version} = useAppVersion()
const route = useRoute()
const { t } = useI18n()
const { sections } = useSidebar()
const translatedSections = computed(() =>
sections.value.map((section) => ({
label: t(section.label),
icon: section.icon,
items: section.items.map((item) => ({
label: t(item.label),
to: item.to,
icon: item.icon,
})),
})),
)
const isAdmin = computed(() => (auth.user?.roles ?? []).includes('ROLE_ADMIN'))
const isEmployee = computed(() => Boolean(auth.user?.isEmployee))
const isAbsenceSectionVisible = computed(() => isEmployee.value || isAdmin.value)
const isMailVisible = computed(() => {
const roles: string[] = auth.user?.roles ?? []