Преглед на файлове

add dropdown menus (ongoing)

Olivier Massot преди 3 години
родител
ревизия
aaacc2e7b8
променени са 31 файла, в които са добавени 365 реда и са изтрити 374 реда
  1. 24 34
      components/Layout/Header.vue
  2. 90 84
      components/Layout/Header/Menu.vue
  3. 10 23
      components/Layout/MainMenu.vue
  4. 3 7
      components/Ui/Image.vue
  5. 0 3
      composables/data/useImageFetch.ts
  6. 146 0
      composables/layout/useMenu.ts
  7. 0 123
      composables/layout/useMenuBuilder.ts
  8. 1 0
      lang/fr.json
  9. 2 10
      layouts/default.vue
  10. 50 0
      services/menuBuilder/_import.ts
  11. 14 8
      services/menuBuilder/abstractMenuBuilder.ts
  12. 1 4
      services/menuBuilder/accessMenuBuilder.ts
  13. 4 6
      services/menuBuilder/accountMenuBuilder.ts
  14. 1 4
      services/menuBuilder/admin2iosMenuBuilder.ts
  15. 1 4
      services/menuBuilder/agendaMenuBuilder.ts
  16. 1 4
      services/menuBuilder/billingMenuBuilder.ts
  17. 1 4
      services/menuBuilder/communicationMenuBuilder.ts
  18. 1 4
      services/menuBuilder/configurationMenuBuilder.ts
  19. 1 4
      services/menuBuilder/cotisationsMenuBuilder.ts
  20. 1 4
      services/menuBuilder/donorsMenuBuilder.ts
  21. 1 4
      services/menuBuilder/educationalMenuBuilder.ts
  22. 1 4
      services/menuBuilder/equipmentMenuBuilder.ts
  23. 1 4
      services/menuBuilder/mainMenuBuilder.ts
  24. 1 4
      services/menuBuilder/medalsMenuBuilder.ts
  25. 1 4
      services/menuBuilder/myAccessesMenuBuilder.ts
  26. 1 4
      services/menuBuilder/myFamilyMenuBuilder.ts
  27. 1 4
      services/menuBuilder/parametersMenuBuilder.ts
  28. 1 4
      services/menuBuilder/statsMenuBuilder.ts
  29. 1 4
      services/menuBuilder/websiteAdminMenuBuilder.ts
  30. 3 6
      services/menuBuilder/websiteListMenuBuilder.ts
  31. 1 2
      types/layout.d.ts

+ 24 - 34
components/Layout/Header.vue

@@ -11,10 +11,10 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
   >
     <template #prepend>
       <v-app-bar-nav-icon
-          v-if="hasMenu('Main')"
-          :icon="isMenuOpened('Main') ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
+          v-if="hasMainMenu"
+          :icon="isMainMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
           class="text-ot-white"
-          @click="toggleMenu('Main')"
+          @click="toggleMainMenu"
       >
       </v-app-bar-nav-icon>
 
@@ -27,25 +27,29 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
 
 <!--    <LayoutHeaderUniversalCreationCreateButton v-if="showUniversalButton" />-->
 
-    <v-tooltip text="$t('welcome')" bottom>
-      <v-btn
-        icon="far fa-home"
-        :href="homeUrl"
-        class="ml-2 text-ot-white"
-      ></v-btn>
+    <v-tooltip :text="$t('welcome')" location="bottom">
+      <template #activator="{ props }">
+        <v-btn
+            v-bind="props"
+            icon="fas fa-home"
+            :href="homeUrl"
+            class="ml-2 text-ot-white"
+            size="small"
+        ></v-btn>
+      </template>
     </v-tooltip>
 
-<!--    <LayoutHeaderMenu :menu="webSiteMenu" />-->
+    <LayoutHeaderMenu name="WebsiteList" />
 
-<!--    <LayoutHeaderMenu v-if="hasAccessesMenu" :menu="myAccessesMenu" />-->
+    <LayoutHeaderMenu name="MyAccesses" />
 
-<!--    <LayoutHeaderMenu v-if="hasFamilyMenu" :menu="myFamilyMenu" />-->
+    <LayoutHeaderMenu name="MyFamily" />
 
 <!--    <LayoutHeaderNotification />-->
 
-<!--    <LayoutHeaderMenu v-if="hasConfigurationMenu" :menu="configurationMenu" />-->
+    <LayoutHeaderMenu name="Configuration" />
 
-<!--    <LayoutHeaderMenu :menu="accountMenu"/>-->
+    <LayoutHeaderMenu name="Account" />
 
     <a
         href="https://support.opentalent.fr/"
@@ -61,8 +65,8 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
 
 <script setup lang="ts">
 
-import {computed, ComputedRef, Ref} from "@vue/reactivity";
-import {useMenuBuilder} from "~/composables/layout/useMenuBuilder";
+import {computed, ComputedRef} from "@vue/reactivity";
+import {useMenu} from "~/composables/layout/useMenu";
 import {useAbility} from "@casl/vue";
 import {usePageStore} from "~/store/page";
 import {useOrganizationProfileStore} from "~/store/profile/organization";
@@ -72,25 +76,11 @@ const organizationProfile = useOrganizationProfileStore()
 
 const title: ComputedRef<string> = computed(() => organizationProfile.name ?? 'Opentalent')
 
-const { buildAccountMenu, buildWebsiteListMenu, buildMyFamilyMenu, buildConfigurationMenu, buildMyAccessesMenu, hasMenu } = useMenuBuilder()
-
-const webSiteMenu: Ref<any> = buildWebsiteListMenu()
-const myAccessesMenu: Ref<any> = buildMyAccessesMenu()
-const myFamilyMenu: Ref<any> = buildMyFamilyMenu()
-const configurationMenu: Ref<any> = buildConfigurationMenu()
-const accountMenu: Ref<any> = buildAccountMenu()
-
-const pageState = usePageStore()
-
-const isMenuOpened = (name: string): boolean => {
-  return pageState.menusOpened[name]
-}
-
-const toggleMenu = (name: string) => {
-  console.log('toggle menu ' + name)
-  pageState.menusOpened[name] = !pageState.menusOpened[name]
-}
+const { hasMenu, isMenuOpened, toggleMenu } = useMenu()
 
+const hasMainMenu = computed(() => hasMenu('Main'))
+const isMainMenuOpened = computed(() => isMenuOpened('Main'))
+const toggleMainMenu = () => toggleMenu('Main')
 
 const runtimeConfig = useRuntimeConfig()
 const homeUrl = runtimeConfig.baseUrlAdminLegacy

+ 90 - 84
components/Layout/Header/Menu.vue

@@ -4,99 +4,105 @@ header principal (configuration, paramètres du compte...)
 -->
 
 <template>
-  <v-menu offset-y left>
-    <template #activator="{ on: { click }, attrs }">
-      <v-tooltip bottom>
-        <template #activator="{ on: on_tooltips , attrs: attrs_tooltips }">
-          <v-btn
-            icon
-            v-bind="[attrs, attrs_tooltips]"
-            color=""
-            v-on="on_tooltips"
-            @click="click"
-          >
-            <v-avatar
-              v-if="menu.icon.avatarByDefault"
-              size="30"
-            >
-              <UiImage :id="menu.icon.avatarId" :imageByDefault="menu.icon.avatarByDefault" :width="30"></UiImage>
-            </v-avatar>
-            <v-icon  v-else class="ot-white--text" small>
-              {{ menu.icon.name }}
-            </v-icon>
-          </v-btn>
-        </template>
-        <span>{{ $t(menu.title) }}</span>
-      </v-tooltip>
-    </template>
-    <v-card scrollable>
-      <v-card-title class="ot-header_menu text-body-2 font-weight-bold">
-        {{$t(menu.title)}}
-      </v-card-title>
-      <v-card-text class="ma-0 pa-0 header-menu">
-        <v-list dense :subheader="true">
-          <template v-for="(item, index) in menu.children">
-            <v-list-item
-              :id="item.title"
-              :key="index"
-              :href="item.isExternalLink ? item.to : undefined"
-              :to="!item.isExternalLink ? item.to : undefined"
-              router
-              exact
-            >
-              <v-list-item-title>
-                <span v-if="item.icon">
-                  <v-avatar
-                    v-if="item.icon.avatarByDefault"
-                    size="30"
+  <v-tooltip
+      v-if="displayMenu"
+      :text="$t(menu.label)"
+      location="bottom"
+  >
+    <template #activator="{ props }">
+      <v-btn v-bind="props" icon width="48px">
+        <v-avatar v-if="menu.icon.avatarId || menu.icon.avatarByDefault" size="30">
+          <UiImage :id="menu.icon.avatarId" :defaultImage="menu.icon.avatarByDefault" :width="30"></UiImage>
+        </v-avatar>
+        <v-icon  v-else class="text-ot-white" small>
+          {{ menu.icon.name }}
+        </v-icon>
+
+        <v-menu
+            :model-value="isOpened()"
+            activator="parent"
+            location="start"
+            @update:modelValue="onStateUpdated"
+            style="top: 48px;"
+        >
+          <v-card>
+            <v-card-title class="ot-header-menu text-body-2 font-weight-bold">
+              {{$t(menu.label)}}
+            </v-card-title>
+
+            <v-card-text class="ma-0 pa-0 header-menu">
+              <v-list density="compact" :subheader="true">
+                <template v-for="(child, index) in menu.children" :key="index">
+                  <v-list-item
+
+                      :id="child.label"
+                      :href="!isInternalLink(child) ? child.to : undefined"
+                      :to="isInternalLink(child) ? child.to : undefined"
                   >
-                    <UiImage :id="item.icon.avatarId" :imageByDefault="item.icon.avatarByDefault" :width="30"></UiImage>
-                  </v-avatar>
-                  <v-icon v-else class="ot-white--text" small>
-                    {{ item.icon.name }}
-                  </v-icon>
-                </span>
-                <span>{{$t(item.title)}}</span>
-              </v-list-item-title>
-            </v-list-item>
-          </template>
-        </v-list>
-      </v-card-text>
-      <v-card-actions class="ma-0 pa-0">
-        <template v-for="(item, index) in menu.actions">
-          <v-list-item
-            :id="item.title"
-            :key="index"
-            :href="item.isExternalLink ? item.to : undefined"
-            :to="!item.isExternalLink ? item.to : undefined"
-            router
-          >
-            <v-list-item-title class="text-body-2 ot-white--text" v-text="$t(item.title)"/>
-          </v-list-item>
-        </template>
-      </v-card-actions>
-    </v-card>
+                    <v-list-item-title>
+                      <span v-if="child.icon">
+                        <v-avatar v-if="menu.icon.avatarId || child.icon.avatarByDefault" size="30">
+                          <UiImage :id="child.icon.avatarId" :defaultImage="child.icon.avatarByDefault" :width="30"></UiImage>
+                        </v-avatar>
+                        <v-icon v-else class="text-ot-white" small>
+                          {{ child.icon.name }}
+                        </v-icon>
+                      </span>
+
+                      <span>{{$t(child.label)}}</span>
+                    </v-list-item-title>
+                  </v-list-item>
+
+                </template>
+              </v-list>
+            </v-card-text>
 
+            <v-card-actions v-if="menu.actions.length > 0" class="ma-0 pa-0 actions">
+              <template v-for="(action, index) in menu.actions" :key="index">
+                <v-list-item
+                    :id="action.label"
+                    :href="!isInternalLink(action) ? action.to : undefined"
+                    :to="isInternalLink(action) ? action.to : undefined"
+                >
+                  <v-list-item-title class="text-body-2" v-text="$t(action.label)"/>
+                </v-list-item>
+              </template>
+            </v-card-actions>
+          </v-card>
+        </v-menu>
+      </v-btn>
+    </template>
+  </v-tooltip>
 
-  </v-menu>
 </template>
 
-<script lang="ts">
-import {defineComponent, useContext} from '@nuxtjs/composition-api'
-import {accessState} from "~/types/interfaces";
+<script setup lang="ts">
+import {useMenu} from "~/composables/layout/useMenu";
+import {computed} from "@vue/reactivity";
 
-export default defineComponent({
-  props: {
-    menu: {
-      type: Object,
-      required: true
-    }
+const props = defineProps({
+  name: {
+    type: String,
+    required: true
   }
 })
+
+const { buildMenu, isInternalLink, hasMenu, setMenuState, isMenuOpened } = useMenu()
+
+const menu = buildMenu(props.name)
+const displayMenu = computed(() => hasMenu(props.name))
+const isOpened = () => isMenuOpened(props.name)
+
+const onStateUpdated = (e: any) => {
+  console.log(props.name, menu.value.label)
+  setMenuState(props.name, e)
+}
+
 </script>
-<style scoped>
-  #logout{
-    background: var(--v-theme-ot-green, white);
+
+<style scoped lang="scss">
+  .actions {
+    background: rgb(var(--v-theme-ot-green));
     color: white;
   }
 </style>

+ 10 - 23
components/Layout/MainMenu.vue

@@ -5,8 +5,8 @@ Prend en paramètre une liste de ItemMenu et les met en forme
 
 <template>
   <v-navigation-drawer
-      :model-value="true"
-      :rail="!opened"
+      v-model="displayMenu"
+      :rail="!isOpened"
       :disable-resize-watcher="true"
       class="bg-ot-dark-grey text-ot-menu-color main-menu"
   >
@@ -74,32 +74,19 @@ Prend en paramètre une liste de ItemMenu et les met en forme
 </template>
 
 <script setup lang="ts">
+import {useMenu} from "~/composables/layout/useMenu";
 import {computed} from "@vue/reactivity";
-import {MenuGroup, MenuItem} from "~/types/layout";
-import {MENU_LINK_TYPE} from "~/types/enum/layout";
-import {usePageStore} from "~/store/page";
-
-const props = defineProps({
-  menu: {
-    type: Object as () => MenuGroup,
-    required: true
-  },
-  miniVariant: {
-    type: Boolean,
-    required: true
-  }
-})
 
-const { menusOpened } = usePageStore()
+const { buildMenu, hasMenu, isInternalLink, openMenu, isMenuOpened } = useMenu()
 
-// Par défaut si l'écran est trop petit au chargement de la page, le menu doit être fermé.
-if(process.client)
-  menusOpened['Main'] = window.innerWidth >= 1264
+const menu = buildMenu('Main')
 
-const opened = computed(() => menusOpened['Main'])
+const displayMenu = computed(() => hasMenu('Main'))
+const isOpened = computed(() => isMenuOpened('Main'))
 
-const isInternalLink = (menuItem: MenuItem | MenuGroup) => {
-  return 'type' in menuItem && menuItem.type === MENU_LINK_TYPE.INTERNAL
+// Par défaut si l'écran est trop petit au chargement de la page, le menu doit rester fermé.
+if (process.client && window.innerWidth >= 1264) {
+    openMenu('Main')
 }
 </script>
 

+ 3 - 7
components/Ui/Image.vue

@@ -84,10 +84,6 @@ const props = defineProps({
 
 // fetchOnServer: false,  // TODO: je ne sais pas encore par quoi remplacer ça...
 
-if (!props.id) {
-  throw new Error("Image's id is missing")
-}
-
 const defaultImagePath = `/images/default/${props.defaultImage}`
 
 const openUpload: Ref<Boolean> = ref(false)
@@ -95,16 +91,16 @@ const imgSrcReload: Ref<any> = ref(null)
 const { $config } = useNuxtApp()
 
 const { fetch } = useImageFetch()
+const fetchImage = () => fetch(props.id ?? null, props.defaultImage, props.height, props.width)
 
-
-const { data: image, pending } = fetch(props.id, props.defaultImage, props.height, props.width)
+const {data: image, pending} = fetchImage()
 
 const unwatch: WatchStopHandle = watch(() => props.id, async (newValue, oldValue) => {
   imgSrcReload.value = await fetch(newValue as number, props.defaultImage, props.height, props.width)
 })
 
 const onReload = () => {
-  fetch()
+  fetchImage()
   openUpload.value = false
 }
 

+ 0 - 3
composables/data/useImageFetch.ts

@@ -1,7 +1,4 @@
 import {useAsyncData, AsyncData, useFetch, FetchResult} from "#app";
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import ApiResource from "~/models/ApiResource";
-import {AssociativeArray} from "~/types/enum/data";
 import {useImageManager} from "~/composables/data/useImageManager";
 import {ref, Ref} from "@vue/reactivity";
 

+ 146 - 0
composables/layout/useMenu.ts

@@ -0,0 +1,146 @@
+import {Ref, ref} from "@vue/reactivity";
+import {useAccessProfileStore} from "~/store/profile/access";
+import {useRuntimeConfig} from "#app";
+import {useAbility} from "@casl/vue";
+import {useOrganizationProfileStore} from "~/store/profile/organization";
+import AbstractMenuBuilder from "~/services/menuBuilder/abstractMenuBuilder";
+import {MenuGroup, MenuItem} from "~/types/layout";
+import {usePageStore} from "~/store/page";
+import menus from "~/services/menuBuilder/_import";
+import {MENU_LINK_TYPE} from "~/types/enum/layout";
+
+/**
+ * Renvoie des méthodes pour interagir avec les menus
+ *
+ * Exemple d'usage :
+ *
+ *     const { buildMenu, hasMenu } = useMenuBuilder()
+ *     const menu = buildMenu('Main')
+ *
+ *     console.log(hasMenu('Main')) // true
+ */
+export const useMenu = () => {
+  const runtimeConfig = useRuntimeConfig()
+  const ability = useAbility()
+  const organizationProfile = useOrganizationProfileStore()
+  const accessProfile = useAccessProfileStore()
+  const pageState = usePageStore()
+
+  const getBuilder = (name: string): AbstractMenuBuilder => {
+    if (!(name in menus)) {
+      throw new Error('Unknown menu : ' + name)
+    }
+    const menuBuilder = menus[name]
+
+    // @ts-ignore
+    return new menuBuilder(runtimeConfig, ability, organizationProfile, accessProfile)
+  }
+
+  /**
+   * Construit un menu à partir de son nom
+   * Met à jour le store pour garder en mémoire la présence de ce menu et son état
+   *
+   * @param name
+   */
+  const buildMenu = (name: string): Ref<MenuGroup> => {
+    const builder = getBuilder(name)
+
+    const menu = builder.build() as MenuGroup
+
+    // On enregistre l'état du menu dans le store de la page
+    if (menu !== null && (menu.children ?? []).length > 0) {
+      pageState.menusOpened[builder.getMenuName()] = false
+      console.log('Menu ' + builder.getMenuName() + ' built (' + (menu.children ?? []).length+ ' entries)')
+    }
+
+    return ref(menu)
+  }
+
+  /**
+   * L'utilisateur en cours a-t-il accès au menu portant ce nom ?
+   *
+   * @param name
+   */
+  const hasMenu = (name: string): boolean => {
+    return name in pageState.menusOpened
+  }
+
+  /**
+   * Soulève une erreur si aucun menu portant ce nom n'est enregistré dans le store
+   *
+   * @param name
+   */
+  const assertExists = (name: string) => {
+    if (!(name in pageState.menusOpened)) {
+      throw new Error('Unknown menu : ' + name)
+    }
+  }
+
+  /**
+   * Retourne vrai si le menu est actuellement ouvert / déplié
+   *
+   * @param name
+   */
+  const isMenuOpened = (name: string): boolean => {
+    assertExists(name)
+    return pageState.menusOpened[name]
+  }
+
+  /**
+   * Met à jour l'état du menu portant ce nom
+   *
+   * @param name
+   * @param state
+   */
+  const setMenuState = (name: string, state: boolean) => {
+    assertExists(name)
+    pageState.menusOpened[name] = state
+  }
+
+  /**
+   * Ouvre / déplie le menu portant ce nom
+   *
+   * @param name
+   */
+  const openMenu = (name: string) => {
+    setMenuState(name, true)
+  }
+
+  /**
+   * Ferme / replie le menu portant ce nom
+   *
+   * @param name
+   */
+  const closeMenu = (name: string) => {
+    setMenuState(name, false)
+  }
+
+  /**
+   * Bascule l'état du menu entre ouvert et fermé
+   *
+   * @param name
+   */
+  const toggleMenu = (name: string) => {
+    setMenuState(name, !pageState.menusOpened[name])
+  }
+
+  /**
+   * Le lien menuItem est-il un lien interne à l'application
+   *
+   * @param menuItem
+   */
+  const isInternalLink = (menuItem: MenuItem | MenuGroup): boolean => {
+    return 'type' in menuItem && menuItem.type === MENU_LINK_TYPE.INTERNAL
+  }
+
+  return {
+    buildMenu,
+    hasMenu,
+    setMenuState,
+    openMenu,
+    closeMenu,
+    toggleMenu,
+    isMenuOpened,
+    isInternalLink
+  }
+}

+ 0 - 123
composables/layout/useMenuBuilder.ts

@@ -1,123 +0,0 @@
-import {Ref, ref} from "@vue/reactivity";
-import {useAccessProfileStore} from "~/store/profile/access";
-import {useRuntimeConfig} from "#app";
-import {useAbility} from "@casl/vue";
-import {useOrganizationProfileStore} from "~/store/profile/organization";
-import AbstractMenuBuilder from "~/services/menuBuilder/abstractMenuBuilder";
-import MainMenuBuilder from "~/services/menuBuilder/mainMenuBuilder";
-import ConfigurationMenuBuilder from "~/services/menuBuilder/configurationMenuBuilder";
-import AccountMenuBuilder from "~/services/menuBuilder/accountMenuBuilder";
-import MyAccessesMenuBuilder from "~/services/menuBuilder/myAccessesMenuBuilder";
-import MyFamilyMenuBuilder from "~/services/menuBuilder/myFamilyMenuBuilder";
-import WebsiteListMenuBuilder from "~/services/menuBuilder/websiteListMenuBuilder";
-import ParametersMenuBuilder from "~/services/menuBuilder/parametersMenuBuilder";
-import {MenuGroup} from "~/types/layout";
-import {usePageStore} from "~/store/page";
-
-/**
- * Renvoie certaines méthodes pour interagir avec les menus
- *
- * La méthode `buildMenu` permet de construire un menu ou un sous-menu.
- * Elle prend en paramètre n'importe quelle subclass de AbstractMenuBuilder
- *
- * La methode `hasMenu` permet d'interroger le store pour savoir si un menu portant ce nom a été
- * construit pour l'utilisateur courant, et si ce menu n'est pas vide.
- *
- * Fournit aussi des méthodes pour créer directement les principaux types de menus :
- *
- *   - buildMainMenu
- *   - buildConfigurationMenu
- *   - buildAccountMenu
- *   - buildMyAccessesMenu
- *   - buildMyFamilyMenu
- *   - buildWebsiteListMenu
- *   - buildParametersMenu
- *
- *
- * Exemple d'usage :
- *
- *     const { buildMenu, hasMenu } = useMenuBuilder()
- *     const menu = buildMenu(MainMenuBuilder)
- *
- *     console.log(hasMenu('Main')) // true
- */
-export const useMenuBuilder = () => {
-  const runtimeConfig = useRuntimeConfig()
-  const ability = useAbility()
-  const organizationProfile = useOrganizationProfileStore()
-  const accessProfile = useAccessProfileStore()
-  const pageState = usePageStore()
-
-  /**
-   * Construit un menu selon le builder passé en paramètre
-   * Dans certains cas, met à jour le profil de l'utilisateur dans le store pour garder une trace de l'état du menu
-   *
-   * @param menuBuilder
-   */
-  const buildMenu = (menuBuilder: typeof AbstractMenuBuilder): Ref<MenuGroup> => {
-    // @ts-ignore
-    const builder = new menuBuilder(runtimeConfig, ability, organizationProfile, accessProfile)
-    const menu = builder.build()
-
-    // On enregistre l'état du menu dans le store de la page
-    if (menu !== null && menu.children.length > 0) {
-      pageState.menusOpened[builder.name()] = false
-      console.log('Menu ' + builder.name() + ' built (' + menu.children.length+ ' entries)')
-    }
-
-    return ref(menu)
-  }
-
-  const hasMenu = (name: string): Ref<boolean> => {
-    // TODO: revoir pour la réactivité
-    return ref(name in pageState.menusOpened)
-  }
-
-  /**
-   * Construit le menu principal
-   */
-
-  const buildMainMenu = () => buildMenu(MainMenuBuilder)
-
-  /**
-   * Construit le menu configuration
-   */
-  const buildConfigurationMenu = () => buildMenu(ConfigurationMenuBuilder)
-
-  /**
-   * Construit le menu Mon Compte
-   */
-  const buildAccountMenu = () => buildMenu(AccountMenuBuilder)
-
-  /**
-   * Construit le menu Mes structure
-   */
-  const buildMyAccessesMenu = () => buildMenu(MyAccessesMenuBuilder)
-
-  /**
-   * Construit le menu Ma famille (changement d'utilisateur)
-   */
-  const buildMyFamilyMenu = () => buildMenu(MyFamilyMenuBuilder)
-
-  /**
-   * Construit le menu de liste des sites internet
-   */
-  const buildWebsiteListMenu = () => buildMenu(WebsiteListMenuBuilder)
-
-  /**
-   * Construit le menu de liste des sites internet
-   */
-  const buildParametersMenu = () => buildMenu(ParametersMenuBuilder)
-
-  return {
-    buildMenu,
-    hasMenu,
-    buildMainMenu,
-    buildConfigurationMenu,
-    buildAccountMenu,
-    buildMyAccessesMenu,
-    buildMyFamilyMenu,
-    buildWebsiteListMenu,
-    buildParametersMenu,
-  }
-}

+ 1 - 0
lang/fr.json

@@ -462,6 +462,7 @@
   "right_menu": "Droits version 5.9",
   "tree_menu": "Gestion de l'arbre",
   "website": "Site internet",
+  "websiteList": "Site(s) internet",
   "advanced_modification": "Administration site internet",
   "simple_modification": "Modifications simplifiées",
   "create": "Créer",

+ 2 - 10
layouts/default.vue

@@ -4,9 +4,9 @@
 <!--    <ClientOnly placeholder-tag="client-only-placeholder" placeholder=" " />-->
     <v-app>
 
-      <LayoutHeader menuOpened="menuOpened" />
+      <LayoutHeader />
 
-      <LayoutMainMenu v-if="displayMenu" :menu="menu" :opened="menuOpened" />
+      <LayoutMainMenu />
 
       <v-main class="ot-content-color">
 <!--        <LayoutSubheader />-->
@@ -24,15 +24,7 @@
 </template>
 
 <script setup lang="ts">
-import {useMenuBuilder} from "~/composables/layout/useMenuBuilder";
-import {computed, ref} from "@vue/reactivity";
 
-const { buildMainMenu, hasMenu } = useMenuBuilder()
-
-const menu = buildMainMenu()
-const displayMenu = computed(() => hasMenu('Main'))
-
-const menuOpened = ref(true)
 </script>
 
 <style scoped>

+ 50 - 0
services/menuBuilder/_import.ts

@@ -0,0 +1,50 @@
+import AccessMenuBuilder from "~/services/menuBuilder/accessMenuBuilder";
+import AccountMenuBuilder from "~/services/menuBuilder/accountMenuBuilder";
+import Admin2iosMenuBuilder from "~/services/menuBuilder/admin2iosMenuBuilder";
+import AgendaMenuBuilder from "~/services/menuBuilder/agendaMenuBuilder";
+import BillingMenuBuilder from "~/services/menuBuilder/billingMenuBuilder";
+import CommunicationMenuBuilder from "~/services/menuBuilder/communicationMenuBuilder";
+import ConfigurationMenuBuilder from "~/services/menuBuilder/configurationMenuBuilder";
+import CotisationsMenuBuilder from "~/services/menuBuilder/cotisationsMenuBuilder";
+import DonorsMenuBuilder from "~/services/menuBuilder/donorsMenuBuilder";
+import EducationalMenuBuilder from "~/services/menuBuilder/educationalMenuBuilder";
+import EquipmentMenuBuilder from "~/services/menuBuilder/equipmentMenuBuilder";
+import MainMenuBuilder from "~/services/menuBuilder/mainMenuBuilder";
+import MedalsMenuBuilder from "~/services/menuBuilder/medalsMenuBuilder";
+import MyAccessesMenuBuilder from "~/services/menuBuilder/myAccessesMenuBuilder";
+import MyFamilyMenuBuilder from "~/services/menuBuilder/myFamilyMenuBuilder";
+import ParametersMenuBuilder from "~/services/menuBuilder/parametersMenuBuilder";
+import StatsMenuBuilder from "~/services/menuBuilder/statsMenuBuilder";
+import WebsiteAdminMenuBuilder from "~/services/menuBuilder/websiteAdminMenuBuilder";
+import WebsiteListMenuBuilder from "~/services/menuBuilder/websiteListMenuBuilder";
+import AbstractMenuBuilder from "~/services/menuBuilder/abstractMenuBuilder";
+
+const imports = [
+    AccessMenuBuilder,
+    AccountMenuBuilder,
+    Admin2iosMenuBuilder,
+    AgendaMenuBuilder,
+    BillingMenuBuilder,
+    CommunicationMenuBuilder,
+    ConfigurationMenuBuilder,
+    CotisationsMenuBuilder,
+    DonorsMenuBuilder,
+    EducationalMenuBuilder,
+    EquipmentMenuBuilder,
+    MainMenuBuilder,
+    MedalsMenuBuilder,
+    MyAccessesMenuBuilder,
+    MyFamilyMenuBuilder,
+    ParametersMenuBuilder,
+    StatsMenuBuilder,
+    WebsiteAdminMenuBuilder,
+    WebsiteListMenuBuilder
+]
+
+const menus: Record<string, typeof AbstractMenuBuilder> = {}
+
+imports.forEach((builder) => {
+    menus[builder.menuName] = builder
+})
+
+export default menus

+ 14 - 8
services/menuBuilder/abstractMenuBuilder.ts

@@ -4,19 +4,23 @@ import {RuntimeConfig} from "@nuxt/schema";
 import Url from "~/services/utils/url";
 import {AnyAbility} from "@casl/ability";
 import {accessState, organizationState} from "~/types/interfaces";
-import {Router} from "vue-router";
 
 /**
  * Classe de base des menus et sous-menus.
  *
  * La méthode principale est la méthode build
  */
-abstract class AbstractMenuBuilder implements MenuBuilder{
+abstract class AbstractMenuBuilder implements MenuBuilder {
   protected runtimeConfig: RuntimeConfig;
   protected ability: AnyAbility;
   protected organizationProfile: organizationState;
   protected accessProfile: accessState;
 
+  /**
+   * Nom court désignant le menu que construit ce builder
+   */
+  static readonly menuName: string
+
   constructor (
       runtimeConfig: RuntimeConfig,
       ability: AnyAbility,
@@ -30,9 +34,11 @@ abstract class AbstractMenuBuilder implements MenuBuilder{
   }
 
   /**
-   * Retourne un nom court pour désigner le menu que construit ce builder
+   * Permet un accès non statique à la variable menuName
    */
-  abstract name(): string
+  public getMenuName(): string {
+    return Object.getPrototypeOf(this).constructor.menuName
+  }
 
   /**
    * Construit et retourne un menu ou sous-menu selon le profil de l'utilisateur, le profil de son organisation
@@ -50,15 +56,17 @@ abstract class AbstractMenuBuilder implements MenuBuilder{
    * @param label
    * @param icon
    * @param {Array<MenuItem>} children Tableau d'ItemMenu représentant les sous menu du menu principal
+   * @param actions
    * @param expanded
    */
   protected createGroup(
       label: string,
       icon?: IconItem,
       children: MenuItems = [],
+      actions: Array<MenuItem> = [],
       expanded: boolean = false
   ): MenuGroup {
-    return { label, icon, children, expanded }
+    return { label, icon, children, actions, expanded }
   }
 
   /**
@@ -68,7 +76,6 @@ abstract class AbstractMenuBuilder implements MenuBuilder{
    * @param {string} label Titre qui sera traduit
    * @param to
    * @param type
-   * @param isAction
    * @return {MenuItem}
    */
   protected createItem (
@@ -76,7 +83,6 @@ abstract class AbstractMenuBuilder implements MenuBuilder{
       icon?: IconItem,
       to: string = '',
       type: MENU_LINK_TYPE = MENU_LINK_TYPE.INTERNAL,
-      isAction = false
   ): MenuItem {
     let url: string
 
@@ -89,7 +95,7 @@ abstract class AbstractMenuBuilder implements MenuBuilder{
         url = to
     }
 
-    return { icon, label, to: url, type, isAction , active: false }
+    return { icon, label, to: url, type, active: false }
   }
 
   protected buildSubmenu(menuBuilder: typeof AbstractMenuBuilder) {

+ 1 - 4
services/menuBuilder/accessMenuBuilder.ts

@@ -7,10 +7,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Répertoire
  */
 export default class AccessMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Access'
-  }
+  static readonly menuName = "Access"
 
   /**
    * Construit le menu Répertoire, ou null si aucune page accessible

+ 4 - 6
services/menuBuilder/accountMenuBuilder.ts

@@ -6,16 +6,14 @@ import AbstractMenuBuilder from '~/services/menuBuilder/abstractMenuBuilder'
  * Menu Mon compte
  */
 export default class AccountMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Account'
-  }
+  static readonly menuName = "Account"
 
   /**
    * Construit le menu Header Configuration ou null si aucune page accessible
    */
   build (): MenuItem | MenuGroup | null {
     const children: MenuItems = []
+    const actions: Array<MenuItem> = []
 
     if (this.ability.can('display', 'my_schedule_page')) {
       children.push(this.createItem('my_schedule_page', undefined, '/my_calendar', MENU_LINK_TYPE.V1))
@@ -77,13 +75,13 @@ export default class AccountMenuBuilder extends AbstractMenuBuilder {
       children.push(this.createItem('print_my_licence', undefined, `/licence_cmf/user`, MENU_LINK_TYPE.V1))
     }
 
-    children.push(this.createItem('logout', undefined, `/logout`, MENU_LINK_TYPE.V1, true))
+    actions.push(this.createItem('logout', undefined, `/logout`, MENU_LINK_TYPE.V1))
 
     const icon = {
       avatarId: this.accessProfile.avatarId,
       avatarByDefault: this.accessProfile.gender == 'MISTER' ? 'men-1.png' : 'women-1.png'
     }
 
-    return this.createGroup('my_account', icon, children)
+    return this.createGroup('my_account', icon, children, actions)
   }
 }

+ 1 - 4
services/menuBuilder/admin2iosMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Admin 2IOS
  */
 export default class Admin2iosMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Admin2ios'
-  }
+  static readonly menuName = "Admin2ios"
 
   /**
    * Construit le menu Administration 2ios ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/agendaMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu agenda
  */
 export default class AgendaMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Agenda'
-  }
+  static readonly menuName = "Agenda"
 
   /**
    * Construit le menu Agenda ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/billingMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Facturation
  */
 export default class BillingMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Billing'
-  }
+  static readonly menuName = "Billing"
 
   /**
    * Construit le menu Facturation ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/communicationMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Communication
  */
 export default class CommunicationMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Communication'
-  }
+  static readonly menuName = "Communication"
 
   /**
    * Construit le menu Communication ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/configurationMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Classe pour la construction du Menu Paramètres
  */
 export default class ConfigurationMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Configuration'
-  }
+  static readonly menuName = "Configuration"
 
   /**
    * Construit le menu Header Configuration ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/cotisationsMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Cotisation (CMF)
  */
 export default class CotisationsMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Cotisation'
-  }
+  static readonly menuName = "Cotisation"
 
   /**
    * Construit le menu Cotisations ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/donorsMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Donneurs
  */
 export default class DonorsMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Donors'
-  }
+  static readonly menuName = "Donors"
 
   /**
    * Construit le menu Partenariat et Dons, ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/educationalMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Suivi pédagogique
  */
 export default class EducationalMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Educational'
-  }
+  static readonly menuName = "Educational"
 
   /**
    * Construit le menu Suivi pédagogique ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/equipmentMenuBuilder.ts

@@ -6,10 +6,7 @@ import AbstractMenuBuilder from "~/services/menuBuilder/abstractMenuBuilder";
  * Menu Matériel
  */
 export default class EquipmentMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Equipment'
-  }
+  static readonly menuName = "Equipment"
 
   /**
    * Construit le menu Equipement ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/mainMenuBuilder.ts

@@ -17,10 +17,7 @@ import Admin2iosMenuBuilder from "~/services/menuBuilder/admin2iosMenuBuilder";
  * Menu principal (ou menu lateral)
  */
 export default class MainMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Main'
-  }
+  static readonly menuName = "Main"
 
   /**
    * Construit le menu principal, ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/medalsMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Médailles
  */
 export default class MedalsMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Medals'
-  }
+  static readonly menuName = "Medals"
 
   /**
    * Construit le menu Médailles et Dons, ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/myAccessesMenuBuilder.ts

@@ -7,10 +7,7 @@ import {useEach} from "#imports";
  * Menu Mon Profil
  */
 export default class MyAccessesMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'MyAccesses'
-  }
+  static readonly menuName = "MyAccesses"
 
   /**
    * Construit le menu Header Multi compte, ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/myFamilyMenuBuilder.ts

@@ -7,10 +7,7 @@ import {useEach} from "#imports";
  * Menu Famille
  */
 export default class MyFamilyMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'MyFamily'
-  }
+  static readonly menuName = "MyFamily"
 
   /**
    * Construit le menu Header Changement d'utilisateur ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/parametersMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Paramètres
  */
 export default class ParametersMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Parameters'
-  }
+  static readonly menuName = "Parameters"
 
   /**
    * Construit le menu Header Configuration, ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/statsMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Statistiques
  */
 export default class StatsMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'Stats'
-  }
+  static readonly menuName = "Stats"
 
   /**
    * Construit le menu Statistique et Dons ou null si aucune page accessible

+ 1 - 4
services/menuBuilder/websiteAdminMenuBuilder.ts

@@ -6,10 +6,7 @@ import {MENU_LINK_TYPE} from "~/types/enum/layout";
  * Menu Site internet
  */
 export default class WebsiteAdminMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'WebsiteAdmin'
-  }
+  static readonly menuName = "WebsiteAdmin"
 
   /**
    * Construit le menu Site internet, ou null si aucune page accessible

+ 3 - 6
services/menuBuilder/websiteListMenuBuilder.ts

@@ -7,10 +7,7 @@ import {useEach} from "#imports";
  * Menu : Liste des sites internet de la structure et de ses structures parentes
  */
 export default class WebsiteListMenuBuilder extends AbstractMenuBuilder {
-
-  name() {
-    return 'WebsiteList'
-  }
+  static readonly menuName = "WebsiteList"
 
   /**
    * Construit le menu Site internet, ou null si aucune page accessible
@@ -23,12 +20,12 @@ export default class WebsiteListMenuBuilder extends AbstractMenuBuilder {
 
     useEach(this.organizationProfile.parents, (parent:any) => {
       if(parent.id != process.env.OPENTALENT_MANAGER_ID){
-        children.push(this.createItem(parent.name, undefined, parent.website, MENU_LINK_TYPE.V1))
+        children.push(this.createItem(parent.name, undefined, parent.website, MENU_LINK_TYPE.EXTERNAL))
       }
     })
 
     if (children.length > 0) {
-      return this.createGroup('website', {name: 'fas fa-globe-americas'},  children)
+      return this.createGroup('websiteList', {name: 'fas fa-globe-americas'},  children)
     }
 
     return null

+ 1 - 2
types/layout.d.ts

@@ -16,8 +16,6 @@ interface MenuItem {
     to?: string
     /** Type de lien (interne, externe, v1)  */
     type: MENU_LINK_TYPE
-    /** Modifie l'affichage de l'item pour signifier une action (ex: se déconnecter) */
-    isAction: boolean
     // TODO: revoir l'utilité de avatar?
     avatar?: number
     /** Correspond à la page actuelle */
@@ -31,6 +29,7 @@ interface MenuGroup {
     label: string
     icon?: IconItem
     children?: MenuItems
+    actions?: MenuItems
     /** Le sous-menu est-il déplié ou non */
     expanded: boolean
 }