Barre horizontale en haut de l'app qui liste les sites autorises de l'utilisateur et permet de switcher d'un click. Consomme le composant MalioSiteSelector de @malio/layer-ui 1.4.0 (upgrade depuis 1.3.0). Composables : - useModules (shared) : consomme /api/modules, expose isModuleActive. Pattern aligne sur useSidebar. - useCurrentSite (layer sites) : singleton state, switchSite optimistic avec rollback sur erreur, garde anti-double-submit, propagation au store auth via action setCurrentSite dediee. Composant : - SiteSelector.vue : wrapper thin autour de MalioSiteSelector. Texte blanc uniforme (conforme maquette Figma) avec taille 24px forcee via labelClass="text-2xl". aria-label du group via ariaGroupLabel i18n. Integration : - Middleware auth.global.ts : chargement parallele sidebar + modules. - layouts/default.vue : render conditionnel si module Sites actif ET user.sites.length > 0. - logout.vue : reset des 3 composables (sidebar, modules, currentSite) dans un try/finally. - nuxt.config.ts : auto-detection des composables/ de chaque layer module (necessaire car imports.dirs explicite override les defaults Nuxt). Couleurs fixtures finales : Chatellerault #056CF2, Saint-Jean #F3CB00, Pommevic #74BF04. Charge aux admins de choisir des teintes foncees (texte blanc non contrastable via calcul WCAG, design choisi). Tests : 40 Vitest (color, useModules, useSidebar, useCurrentSite, SiteSelector) incluant garde anti-regression pour useI18n hors setup. 182/182 PHPUnit backend, avec et sans module actif. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.1 KiB
Vue
68 lines
2.1 KiB
Vue
<template>
|
|
<div class="h-screen overflow-hidden">
|
|
<div class="flex h-full">
|
|
<MalioSidebar
|
|
v-model="ui.sidebarCollapsed"
|
|
:sections="translatedSections"
|
|
>
|
|
<template #logo>
|
|
<img src="/LOGO_MALIO.png" alt="Malio"/>
|
|
</template>
|
|
<template #logo-collapsed>
|
|
<img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio"/>
|
|
</template>
|
|
</MalioSidebar>
|
|
|
|
<div class="h-full flex-1 flex flex-col min-h-0 min-w-0">
|
|
<SiteSelector v-if="showSiteSelector"/>
|
|
<main
|
|
class="flex flex-1 flex-col overflow-y-auto overflow-x-hidden bg-white px-4 pb-24 sm:px-8 lg:px-16">
|
|
<div
|
|
aria-hidden="true"
|
|
class="pointer-events-none sticky top-0 z-30 h-8 flex-shrink-0 bg-white sm:h-12"/>
|
|
<slot/>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const {t} = useI18n()
|
|
const ui = useUiStore()
|
|
const {sections} = useSidebar()
|
|
const {isModuleActive} = useModules()
|
|
const auth = useAuthStore()
|
|
const route = useRoute()
|
|
|
|
// Le SiteSelector est rendu si :
|
|
// - le module Sites est actif dans config/modules.php (sinon la feature
|
|
// n'a pas de sens, cf. ticket 3 spec criteres d'acceptation) ;
|
|
// - ET l'user connecte a au moins un site autorise (sinon "barre vide"
|
|
// sans tile cliquable).
|
|
// Les deux flags sont resolus par le middleware auth.global.ts avant
|
|
// que le layout ne soit rendu (plan load parallele), donc pas de flash.
|
|
const showSiteSelector = computed(() =>
|
|
isModuleActive('sites') && (auth.user?.sites?.length ?? 0) > 0,
|
|
)
|
|
|
|
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,
|
|
})),
|
|
}))
|
|
)
|
|
|
|
watch(() => route.path, () => {
|
|
ui.closeMobileSidebar()
|
|
})
|
|
|
|
useHead({
|
|
titleTemplate: (title) => title || 'Coltura',
|
|
})
|
|
</script>
|