Vincent 3 years ago
parent
commit
dd15fcee93
46 changed files with 3037 additions and 36 deletions
  1. 3 1
      app.vue
  2. 28 0
      components/Layout/BannerTop.vue
  3. 18 0
      components/Layout/Container.vue
  4. 89 0
      components/Layout/Dialog.vue
  5. 146 0
      components/Layout/Header.vue
  6. 102 0
      components/Layout/Header/Menu.vue
  7. 262 0
      components/Layout/Header/Notification.vue
  8. 72 0
      components/Layout/Header/UniversalCreation/CreateButton.vue
  9. 148 0
      components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue
  10. 91 0
      components/Layout/Header/UniversalCreation/TypeCard.vue
  11. 52 0
      components/Layout/LoadingScreen.vue
  12. 152 0
      components/Layout/Menu.vue
  13. 62 0
      components/Layout/SubHeader/ActivityYear.vue
  14. 48 0
      components/Layout/SubHeader/Breadcrumbs.vue
  15. 93 0
      components/Layout/SubHeader/DataTiming.vue
  16. 108 0
      components/Layout/SubHeader/DataTimingRange.vue
  17. 117 0
      components/Layout/SubHeader/PersonnalizedList.vue
  18. 51 0
      components/Layout/Subheader.vue
  19. 0 12
      components/UserBlock.vue
  20. 66 0
      composables/layout/Menus/accessMenu.ts
  21. 106 0
      composables/layout/Menus/accountMenu.ts
  22. 69 0
      composables/layout/Menus/admin2iosMenu.ts
  23. 45 0
      composables/layout/Menus/agendaMenu.ts
  24. 56 0
      composables/layout/Menus/baseMenu.ts
  25. 72 0
      composables/layout/Menus/billingMenu.ts
  26. 52 0
      composables/layout/Menus/communicationMenu.ts
  27. 95 0
      composables/layout/Menus/configurationMenu.ts
  28. 108 0
      composables/layout/Menus/cotisationsMenu.ts
  29. 36 0
      composables/layout/Menus/donorsMenu.ts
  30. 68 0
      composables/layout/Menus/educationalMenu.ts
  31. 36 0
      composables/layout/Menus/equipmentMenu.ts
  32. 36 0
      composables/layout/Menus/medalsMenu.ts
  33. 40 0
      composables/layout/Menus/myAccessesMenu.ts
  34. 52 0
      composables/layout/Menus/myFamilyMenu.ts
  35. 57 0
      composables/layout/Menus/parametersMenu.ts
  36. 56 0
      composables/layout/Menus/statsMenu.ts
  37. 62 0
      composables/layout/Menus/websiteMenu.ts
  38. 143 0
      composables/layout/useMenu.ts
  39. 59 0
      layouts/default.vue
  40. 40 0
      nuxt.config.ts
  41. 2 0
      package.json
  42. 3 15
      pages/index.vue
  43. 11 0
      plugins/vuetify.ts
  44. 3 5
      services/profile/organizationProfile.ts
  45. 1 1
      types/interfaces.d.ts
  46. 21 2
      yarn.lock

+ 3 - 1
app.vue

@@ -1,3 +1,5 @@
 <template>
-  <NuxtPage />
+  <NuxtLayout>
+    <NuxtPage />
+  </NuxtLayout>
 </template>

+ 28 - 0
components/Layout/BannerTop.vue

@@ -0,0 +1,28 @@
+<!-- Troisième bandeau en partant du haut, contenant entre autre le numéro SIRET de l'organisation -->
+
+<template>
+  <v-row justify="center" align="center" class="bannerTopForm">
+    <v-col cols="3" class="ot_dark_grey ot_white--text">
+      <slot name="block1" />
+    </v-col>
+    <v-col cols="6" class="ot_white ot_grey--text">
+      <slot name="block2" />
+    </v-col>
+    <v-col cols="3" class="ot_light_grey ot_grey--text">
+      <slot name="block3" />
+    </v-col>
+  </v-row>
+</template>
+
+<style scoped>
+  .bannerTopForm{
+    min-height: 100px;
+    margin-top: 10px !important;
+    margin-bottom: 10px !important;
+  }
+  .bannerTopForm > .col{
+    min-height: 100px;
+    padding: 10px;
+    padding-left: 24px;
+  }
+</style>

+ 18 - 0
components/Layout/Container.vue

@@ -0,0 +1,18 @@
+<!-- Container générique pleine page, utilisé entre autres
+ pour porter le contenu principal de la page -->
+
+<template>
+  <v-container fluid class="container">
+    <v-row justify="center" align="center">
+      <v-col cols="12" sm="12" md="12">
+        <slot />
+      </v-col>
+    </v-row>
+  </v-container>
+</template>
+
+<style scoped>
+  .container{
+    padding-top: 0;
+  }
+</style>

+ 89 - 0
components/Layout/Dialog.vue

@@ -0,0 +1,89 @@
+<!-- Fenêtre de dialogue -->
+<template>
+  <v-dialog
+    v-model="show"
+    persistent
+    max-width="800"
+    :content-class="contentClass"
+  >
+    <v-card  class="d-flex">
+
+        <div class="dialog-type flex-column justify-center d-none d-sm-flex">
+          <h3 class="d-flex"> <slot name="dialogType"></slot></h3>
+        </div>
+
+        <div class="dialog-container flex-column flex-grow-1">
+          <div class="d-flex flex-column">
+            <v-card-title class="dialog-title">
+              <slot name="dialogTitle"></slot>
+            </v-card-title>
+            <div class="dialog-text-container">
+              <slot name="dialogText" />
+            </div>
+
+            <v-divider />
+
+            <v-card-actions class="justify-center">
+              <slot name="dialogBtn" />
+            </v-card-actions>
+          </div>
+
+        </div>
+
+    </v-card>
+  </v-dialog>
+</template>
+
+<script lang="ts">
+import { defineComponent } from '@nuxtjs/composition-api'
+
+export default defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      required: true
+    },
+    contentClass: {
+      type: String,
+      required: false
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .dialog-title{
+    background: #e6e6e6;
+    padding-left: 40px;
+    font-weight: normal;
+  }
+  .dialog-type{
+    background: var(--v-ot_green-base, #00AD8E);
+    color: #fff;
+    width: 160px;
+   h3{
+     font-size: 25px;
+     font-weight: normal;
+     writing-mode: tb-lr;
+     writing-mode: vertical-lr;
+     transform: rotate(
+         -180deg);
+     padding: 10px;
+    }
+  }
+
+  .dialog-text-container{
+    max-height: 70vh;
+    overflow: auto;
+  }
+  .modal-level-alert{
+    .dialog-type{
+      background: var(--v-ot_danger-base, #f56954);
+    }
+  }
+  .modal-level-warning{
+    .dialog-type{
+      background: var(--v-ot_warning-base, #f39c12);
+    }
+  }
+</style>

+ 146 - 0
components/Layout/Header.vue

@@ -0,0 +1,146 @@
+<!--
+Header de l'application, contient entre autres le nom de l'organisation, l'accès à l'aide
+et aux préférences de l'utilisateur
+-->
+
+<template>
+  <v-app-bar
+    clipped-left
+    elevate-on-scroll
+    dense
+    fixed
+    app
+    class="ot_green ot_white--text"
+  >
+    <v-btn
+      v-if="displayedMiniVariant"
+      class="menu-btn d-none d-sm-none d-sm-none d-md-none d-lg-flex" icon @click.stop="displayedMiniMenu()"
+    >
+      <v-icon class="ot_white--text">
+        mdi-menu{{ `${properties.miniVariant ? '' : '-open'}` }}
+      </v-icon>
+    </v-btn>
+
+    <v-btn class="menu-btn d-sm-flex d-md-flex d-lg-none" icon @click.stop="displayedMenu()">
+      <v-icon class="ot_white--text">
+        mdi-menu
+      </v-icon>
+    </v-btn>
+
+    <v-toolbar-title v-text="title" />
+
+    <v-spacer />
+
+    <LayoutHeaderUniversalCreationCreateButton v-if="showUniversalButton" />
+
+    <v-tooltip bottom>
+      <template #activator="{ on, attrs }">
+        <v-btn
+          icon
+          class="ml-2"
+          v-bind="attrs"
+          v-on="on"
+        >
+          <a class="no-decoration" :href="properties.homeUrl + '/'"><v-icon class="ot_white--text" small>fa-home</v-icon></a>
+        </v-btn>
+      </template>
+      <span>{{ $t('welcome') }}</span>
+    </v-tooltip>
+
+    <LayoutHeaderMenu :menu="webSiteMenu" />
+
+    <LayoutHeaderMenu v-if="hasAccessesMenu" :menu="myAccessesMenu" />
+
+    <LayoutHeaderMenu v-if="hasFamilyMenu" :menu="myFamilyMenu" />
+
+    <LayoutHeaderNotification />
+
+    <LayoutHeaderMenu v-if="hasConfigurationMenu" :menu="configurationMenu" />
+
+    <LayoutHeaderMenu :menu="accountMenu"/>
+
+    <a class="text-body pa-3 ml-2 ot_dark_grey ot_white--text text-decoration-none" href="https://support.opentalent.fr/" target="_blank">
+      <span class="d-none d-sm-none d-md-flex">{{ $t('help_access') }}</span>
+      <v-icon class="d-sm-flex d-md-none" color="white">fas fa-question-circle</v-icon>
+    </a>
+  </v-app-bar>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent, reactive, useContext, computed, ComputedRef, Ref, UnwrapRef
+} from '@nuxtjs/composition-api'
+import { $useMenu } from '~/composables/layout/menu'
+import { AnyJson } from '~/types/interfaces'
+
+export default defineComponent({
+  setup (_props, { emit }) {
+    const { store, $config,$ability } = useContext()
+
+    const properties:UnwrapRef<AnyJson> = reactive({
+      miniVariant: false,
+      openMenu: false,
+      homeUrl: $config.baseURL_adminLegacy
+    })
+
+    const displayedMiniVariant:ComputedRef<boolean> = computed(() => store.state.profile.access.hasLateralMenu)
+    const hasConfigurationMenu:ComputedRef<boolean> = computed(() => store.state.profile.access.hasConfigurationMenu)
+    const hasAccessesMenu:ComputedRef<boolean> = computed(() => store.state.profile.access.hasAccessesMenu)
+    const hasFamilyMenu:ComputedRef<boolean> = computed(() => store.state.profile.access.hasFamilyMenu)
+    const title:ComputedRef<string> = computed(() => store.state.profile.organization.name)
+
+    const webSiteMenu:Ref<any> = $useMenu.setupContext().useWebSiteMenuConstruct()
+    const myAccessesMenu:Ref<any> = $useMenu.setupContext().useMyAccessesMenuConstruct()
+    const myFamilyMenu:Ref<any> = $useMenu.setupContext().useMyFamilyMenuConstruct()
+    const configurationMenu:Ref<any> = $useMenu.setupContext().useConfigurationMenuConstruct()
+    const accountMenu:Ref<any> = $useMenu.setupContext().useAccountMenuConstruct()
+
+    const displayedMiniMenu = () => {
+      properties.miniVariant = !properties.miniVariant
+      emit('handle-open-mini-menu-click', properties.miniVariant)
+    }
+
+    const displayedMenu = () => {
+      properties.openMenu = !properties.openMenu
+      emit('handle-open-menu-click', properties.openMenu )
+    }
+
+    const showUniversalButton =
+        $ability.can('manage', 'users')
+      || $ability.can('manage', 'courses')
+      || $ability.can('manage', 'examens')
+      || $ability.can('manage', 'educationalprojects')
+      || $ability.can('manage', 'events')
+      || $ability.can('manage', 'emails')
+      || $ability.can('manage', 'mails')
+      || $ability.can('manage', 'texto')
+      || $ability.can('display', 'message_send_page')
+      || $ability.can('manage', 'equipments') ;
+
+    return {
+      properties,
+      displayedMiniVariant,
+      hasConfigurationMenu,
+      hasAccessesMenu,
+      hasFamilyMenu,
+      title,
+      displayedMiniMenu,
+      displayedMenu,
+      webSiteMenu,
+      myAccessesMenu,
+      myFamilyMenu,
+      configurationMenu,
+      accountMenu,
+      showUniversalButton
+    }
+  }
+})
+</script>
+
+<style scoped>
+  .help {
+    padding: 14px 14px 13px;
+    font-size: 14px;
+    text-decoration: none;
+  }
+</style>

+ 102 - 0
components/Layout/Header/Menu.vue

@@ -0,0 +1,102 @@
+<!--
+Menu déroulant générique pour l'affichage des menus du
+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"
+                  >
+                    <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-menu>
+</template>
+
+<script lang="ts">
+import {defineComponent, useContext} from '@nuxtjs/composition-api'
+import {accessState} from "~/types/interfaces";
+
+export default defineComponent({
+  props: {
+    menu: {
+      type: Object,
+      required: true
+    }
+  }
+})
+</script>
+<style scoped>
+  #logout{
+    background: var(--v-ot_green-base, white);
+    color: white;
+  }
+</style>

+ 262 - 0
components/Layout/Header/Notification.vue

@@ -0,0 +1,262 @@
+<template>
+  <v-menu offset-y v-model="isOpen">
+    <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-badge
+              color="orange"
+              offset-y="10"
+              :value="unreadNotification.length > 0"
+              :content="unreadNotification.length"
+            >
+              <v-icon class="ot_white--text" small>
+                fa-bell
+              </v-icon>
+            </v-badge>
+          </v-btn>
+        </template>
+        <span>{{ $t('notification') }}</span>
+      </v-tooltip>
+    </template>
+    <v-card scrollable max-width="400">
+      <v-card-title class="ot_header_menu text-body-2 font-weight-bold">
+        {{ $t('notification') }}
+      </v-card-title>
+      <v-card-text class="ma-0 pa-0 header-menu">
+        <v-list dense :subheader="true">
+          <template v-for="(notification, index) in notifications">
+            <v-list-item :key="index" :class="`${notification.notificationUsers.length === 0 ? 'unread' : ''}`">
+              <v-list-item-content>
+                <v-list-item-title class="list_item mt-2 mb-2" v-text="getMessage(notification)"/>
+              </v-list-item-content>
+              <v-list-item-icon v-if="notification.link" class="pt-4">
+                <v-icon @click="download(notification.link)">mdi-download</v-icon>
+              </v-list-item-icon>
+            </v-list-item>
+            <v-divider></v-divider>
+          </template>
+        </v-list>
+        <v-card v-intersect="update"></v-card>
+        <v-row
+          v-if="loading"
+          class="fill-height mt-3 mb-3"
+          align="center"
+          justify="center"
+        >
+          <v-progress-circular
+            indeterminate
+            color="grey lighten-1"
+          ></v-progress-circular>
+        </v-row>
+      </v-card-text>
+      <v-card-actions class="ma-0 pa-0">
+        <template>
+          <v-list-item
+            id="all_notifications"
+            :key="$t('all_notification')"
+            :href="notificationUrl"
+            router
+          >
+            <v-list-item-title class="text-body-2 ot_white--text" v-text="$t('all_notification')"/>
+          </v-list-item>
+        </template>
+      </v-card-actions>
+    </v-card>
+  </v-menu>
+</template>
+
+<script lang="ts">
+import {computed, ComputedRef, defineComponent, onUnmounted, Ref, ref, useContext, useFetch, watch} from '@nuxtjs/composition-api'
+import {NOTIFICATION_TYPE, QUERY_TYPE} from "~/types/enums";
+import {Notification} from "~/models/Core/Notification";
+import {repositoryHelper} from "~/services/store/repository";
+import {ApiResponse, HydraMetadata} from "~/types/interfaces";
+import {queryHelper} from "~/services/store/query";
+import {NotificationUsers} from "~/models/Core/NotificationUsers";
+import {$accessProfile} from "~/services/profile/accessProfile";
+
+export default defineComponent({
+  setup: function () {
+    const {$dataProvider, $dataPersister, $config, store, app: { i18n }} = useContext()
+    const profileAccess = store.state.profile.access
+    $accessProfile.setStore(store)
+    const currentAccessId = $accessProfile.getCurrentAccessId()
+
+    const loading: Ref<Boolean> = ref(true)
+    const isOpen: Ref<Boolean> = ref(false)
+    const page: Ref<number> = ref(1)
+    const data: Ref<ApiResponse> = ref({} as ApiResponse)
+
+    /**
+     * On récupère les notifications via l'API qui seront stockées dans le store
+     */
+    const {fetch, fetchState} = useFetch(async () => {
+      data.value = await $dataProvider.invoke({
+        type: QUERY_TYPE.MODEL,
+        model: Notification,
+        listArgs: {
+          itemsPerPage: 10,
+          page: page.value
+        }
+      })
+      loading.value = false
+    })
+
+    /**
+     * On récupère les Notifications via le store
+     */
+    const notifications: ComputedRef = computed(() => {
+      const query = repositoryHelper.getRepository(Notification).with('message').orderBy('id', 'desc')
+      return queryHelper.getCollection(query)
+    })
+
+    /**
+     * on calcul le nombre de notification non lues
+     */
+    const unreadNotification: ComputedRef<Array<Notification>> = computed(() => {
+      return notifications.value.filter((notification: Notification) => {
+        return notification.notificationUsers.length === 0
+      })
+    })
+
+    /**
+     * Les metadata dépendront de la dernière valeur du GET lancé
+     */
+    const metadata: ComputedRef<HydraMetadata> = computed(() => {
+      return data.value.metadata
+    })
+
+    /**
+     * Lorsque l'utilisateur scroll on regarde la nextPage a charger et on le fait que si le pending du fetch est false
+     * (si on a fini de télécharger les éléments précédents)
+     */
+    const update = async () => {
+      if (!fetchState.pending && metadata.value?.nextPage && metadata.value.nextPage > 0) {
+        loading.value = true
+        page.value = metadata.value.nextPage
+        await fetch()
+        //Si des notifications n'avaient pas été marquées comme lues, on le fait immédiatement.
+        markNotificationsAsRead()
+      }
+    }
+
+    /**
+     * On construit le message qui va devoir s'afficher pour une notification
+     * @param notification
+     */
+    const getMessage = (notification:Notification) => {
+      switch (notification.type){
+        case NOTIFICATION_TYPE.FILE :
+         return `${i18n.t('your_file')} ${notification.message?.fileName} ${i18n.t('is_ready_to_be_downloaded')}`
+          break;
+
+         case NOTIFICATION_TYPE.MESSAGE:
+           if(notification.message?.action)
+             return `${i18n.t('your_message')} ${notification.message?.fileName} ${i18n.t('is_ready_to_be')} ${notification.message.action}`
+
+           return `${i18n.t('your_message')} ${notification.message?.about ?? ''} ${i18n.t('has_been_sent')} `
+           break;
+
+        case NOTIFICATION_TYPE.SYSTEM :
+          if(notification.message?.about)
+            return `${i18n.t(notification.message.about)}`
+          break;
+
+        default:
+          return i18n.t(notification.name)
+      }
+    }
+
+    /**
+     * Dès l'ouverture du menu, on indique que les notifications non lues, le sont.
+     */
+    const unwatch = watch(isOpen, (newValue, oldValue) => {
+      if(newValue){
+        markNotificationsAsRead()
+      }
+    })
+
+    onUnmounted(() => {
+      unwatch()
+    })
+
+    /**
+     * Marque les notification non lues comme lues
+     */
+    const markNotificationsAsRead = () => {
+      unreadNotification.value.map((notification:Notification)=>{
+        notification.notificationUsers = ['read']
+        repositoryHelper.persist(Notification, notification)
+        createNewNotificationUsers(notification)
+      })
+    }
+
+    /**
+     * Créer une nouvelle notification users coté back.
+     * @param notification
+     */
+    const createNewNotificationUsers = (notification: Notification) =>{
+      const newNotificationUsers = repositoryHelper.persist(NotificationUsers, new NotificationUsers(
+        {
+          access:`/api/accesses/${currentAccessId}`,
+          notification:`/api/notifications/${notification.id}`,
+          isRead: true
+        }
+      )) as NotificationUsers
+
+      $dataPersister.invoke({
+        type: QUERY_TYPE.MODEL,
+        model: NotificationUsers,
+        idTemp: newNotificationUsers.id,
+        showProgress: false
+      })
+    }
+
+    /**
+     * Download le lien
+     * @param link
+     */
+    const download = (link: string) => {
+      const url_parts: Array<string> = link.split('/api');
+      if(profileAccess.originalAccess)
+        url_parts[0] = `api/${profileAccess.originalAccess.id}/${currentAccessId}`
+      else
+        url_parts[0] = `api/${currentAccessId}`
+
+      window.open(`${$config.baseURL_Legacy}/${url_parts.join('')}`);
+    }
+
+    return {
+      data,
+      getMessage,
+      notificationUrl: `${$config.baseURL_adminLegacy}/notifications/list/`,
+      loading,
+      notifications,
+      update,
+      unreadNotification,
+      isOpen,
+      download
+    }
+  }
+})
+</script>
+
+<style scoped>
+  #all_notifications{
+    background: var(--v-ot_green-base, white);
+    color: white;
+  }
+  .list_item{
+    white-space: normal;
+  }
+  .unread{
+    background: #ecf0f5;
+  }
+</style>

+ 72 - 0
components/Layout/Header/UniversalCreation/CreateButton.vue

@@ -0,0 +1,72 @@
+<!--
+bouton Créer
+-->
+
+<template>
+  <main>
+    <v-btn
+      elevation="2"
+      color="ot_warning ot_white--text"
+      @click="showDialog=true"
+    >
+      {{ $t('create') }}
+    </v-btn>
+    <lazy-LayoutDialog
+      :show="showDialog"
+    >
+
+      <template #dialogType>{{ $t('creative_assistant') }}</template>
+      <template #dialogTitle>
+        <span v-if="type=='home'">{{ $t('what_do_you_want_to_create') }}</span>
+        <span v-else-if="type=='access'">{{ $t('universal_create_title_access') }}</span>
+        <span v-else-if="type=='event'">{{ $t('universal_create_title_event') }}</span>
+        <span v-else-if="type=='message'">{{ $t('universal_create_title_message') }}</span>
+      </template>
+      <template #dialogText>
+        <LayoutHeaderUniversalCreationGenerateCardsSteps :step="step" @updateStep="updateStep" />
+      </template>
+      <template #dialogBtn>
+        <div class="text-center">
+          <v-btn
+            color="ot_super_light_grey"
+            @click="showDialog=false;step=1;type='home'"
+          >
+            {{ $t('cancel') }}
+          </v-btn>
+          <v-btn
+            v-if="step > 1"
+            color="ot_super_light_grey"
+            @click="step=1;type='home'"
+          >
+            {{ $t('previous') }}
+          </v-btn>
+        </div>
+      </template>
+    </lazy-LayoutDialog>
+  </main>
+</template>
+
+<script lang="ts">
+import {defineComponent, Ref,ref} from '@nuxtjs/composition-api'
+
+export default defineComponent({
+  setup () {
+    const showDialog:Ref<Boolean> = ref(false);
+    const step:Ref<Number> = ref(1);
+    const type:Ref<String> = ref('home');
+    const updateStep = ({stepChoice, typeChoice}: any) =>{
+      step.value = stepChoice
+      type.value = typeChoice
+    }
+    return {
+      showDialog,
+      updateStep,
+      step,
+      type
+    }
+  }
+})
+</script>
+<style scoped>
+
+</style>

+ 148 - 0
components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue

@@ -0,0 +1,148 @@
+<!--
+
+-->
+
+<template>
+  <v-stepper v-model="step"
+  >
+    <v-stepper-items>
+      <v-stepper-content step="1">
+        <div class="row">
+          <LayoutHeaderUniversalCreationTypeCard
+            v-if="$can('manage', 'users')"
+            title="a_person"
+            text-content="add_new_person_student"
+            icon="fa fa-user"
+            type="access"
+            @typeClick="onTypeClick"
+          />
+          <LayoutHeaderUniversalCreationTypeCard
+            v-if="$can('display', 'agenda_page')
+                && ($ability.can('display', 'course_page')
+            || $ability.can('display', 'exam_page')
+            || $ability.can('display', 'pedagogics_project_page'))"
+            title="an_event"
+            text-content="add_an_event_course"
+            icon="fa fa-calendar-alt"
+            type="event"
+            @typeClick="onTypeClick"
+          />
+          <LayoutHeaderUniversalCreationTypeCard
+            v-else-if="$can('display', 'agenda_page') && $can('manage', 'events')"
+            title="other_event" text-content="other_event_text_creation_card"
+            icon="far fa-calendar" :link="adminLegacy+ '/calendar/create/events'" />
+
+          <LayoutHeaderUniversalCreationTypeCard
+            v-if="$can('display', 'message_send_page')
+            && ($ability.can('manage', 'emails')
+            || $ability.can('manage', 'mails')
+            || $ability.can('manage', 'texto'))"
+            title="a_correspondence"
+            text-content="sen_email_letter"
+            icon="fa fa-comment"
+            type="message"
+            @typeClick="onTypeClick"
+          />
+          <LayoutHeaderUniversalCreationTypeCard
+            v-if="$can('manage', 'equipments')"
+            title="a_materiel"
+            text-content="add_any_type_material"
+            icon="fa fa-cube"
+            :link="adminLegacy+ '/list/create/equipment'"
+          />
+        </div>
+      </v-stepper-content>
+
+      <v-stepper-content step="2">
+        <div class="row">
+          <div v-if="type === 'access'" class="row">
+            <LayoutHeaderUniversalCreationTypeCard v-if="isLaw1901" title="an_adherent" text-content="adherent_text_creation_card" icon="fa fa-user" :link="adminLegacy+ '/universal_creation_person/adherent'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard v-if="isLaw1901" title="a_ca_member" text-content="ca_member_text_creation_card" icon="fa fa-users" :link="adminLegacy+ '/universal_creation_person/ca_member'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard title="a_student" text-content="student_text_creation_card" icon="fa fa-user" :link="adminLegacy+ '/universal_creation_person/student'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard title="a_guardian" text-content="guardian_text_creation_card" icon="fa fa-female" :link="adminLegacy+ '/universal_creation_person/guardian'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard title="a_teacher" text-content="teacher_text_creation_card" icon="fa fa-graduation-cap" :link="adminLegacy+ '/universal_creation_person/teacher'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard title="a_member_of_staff" text-content="personnel_text_creation_card" icon="fa fa-suitcase" :link="adminLegacy+ '/universal_creation_person/personnel'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard title="a_legal_entity" text-content="moral_text_creation_card" icon="fa fa-building" :link="adminLegacy+ '/universal_creation_person/company'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard title="another_type_of_contact" text-content="other_contact_text_creation_card" icon="fa fa-plus" :link="adminLegacy+ '/universal_creation_person/other_contact'" ></LayoutHeaderUniversalCreationTypeCard>
+          </div>
+          <div v-if="type === 'event'" class="row">
+            <LayoutHeaderUniversalCreationTypeCard v-if="$can('display', 'course_page')" title="course" text-content="course_text_creation_card" icon="fa fa-users" :link="adminLegacy+ '/calendar/create/courses'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard v-if="$can('display', 'exam_page')" title="exam" text-content="exam_text_creation_card" icon="fa fa-graduation-cap" :link="adminLegacy+ '/calendar/create/examens'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard v-if="$can('display', 'pedagogics_project_page')" title="educational_services" text-content="educational_services_text_creation_card" icon="fa fa-suitcase" :link="adminLegacy+ '/calendar/create/educational_projects'" ></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard v-if="$can('manage', 'events')" title="other_event" text-content="other_event_text_creation_card" icon="far fa-calendar" :link="adminLegacy+ '/calendar/create/events'" ></LayoutHeaderUniversalCreationTypeCard>
+          </div>
+          <div v-if="type === 'message'" class="row">
+            <LayoutHeaderUniversalCreationTypeCard v-if="$can('manage', 'emails')" title="an_email" text-content="email_text_creation_card" icon="far fa-envelope" :link="adminLegacy+ '/list/create/emails'"></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard v-if="$can('manage', 'mails')" title="a_letter" text-content="letter_text_creation_card" icon="far fa-file-alt" :link="adminLegacy+ '/list/create/mails'"></LayoutHeaderUniversalCreationTypeCard>
+            <LayoutHeaderUniversalCreationTypeCard v-if="$can('manage', 'texto')" title="an_sms" text-content="sms_text_creation_card" icon="fa fa-mobile-alt" :link="adminLegacy+ '/list/create/sms'"></LayoutHeaderUniversalCreationTypeCard>
+          </div>
+        </div>
+
+      </v-stepper-content>
+
+    </v-stepper-items>
+  </v-stepper>
+</template>
+
+<script lang="ts">
+import {defineComponent, ref, Ref, useContext} from '@nuxtjs/composition-api'
+import { $organizationProfile } from '~/services/profile/organizationProfile'
+
+export default defineComponent({
+  props: {
+    step: {
+      type: Number,
+      required: true
+    }
+  },
+  setup (_,{emit}) {
+    const { $config, store } = useContext()
+
+    const onTypeClick = (step:Number,Cardtype:String)=>{
+      type.value = Cardtype;
+      emit('updateStep', {stepChoice: step, typeChoice: Cardtype});
+    }
+    const type:Ref<String> = ref('');
+    const organizationProfile = $organizationProfile(store)
+
+    return {
+      type,
+      onTypeClick,
+      adminLegacy: $config.baseURL_adminLegacy,
+      isLaw1901: organizationProfile.isAssociation()
+    }
+  }
+})
+</script>
+<style lang="scss" scoped>
+  .creation-type-container{
+    border: none!important;
+    .icon{
+      i{
+        font-size: 50px;
+        color: var(--v-ot_grey-base, #777777);
+      }
+    }
+    .infos-container{
+      padding: 15px 0;
+      h4{
+        font-size: 15px;
+        color: var(--v-ot_green-base, #00AD8E);
+        font-weight: bold;
+        margin-bottom: 6px;
+      }
+      p{
+        font-size: 13px;
+        padding: 0;
+        margin: 0;
+        color: #767676;
+      }
+    }
+    &>div{
+      &:hover{
+        cursor: pointer;
+        background: var(--v-ot_light_green-base, #a9e0d6);
+      }
+    }
+  }
+</style>

+ 91 - 0
components/Layout/Header/UniversalCreation/TypeCard.vue

@@ -0,0 +1,91 @@
+<!--
+
+-->
+
+<template>
+
+    <v-card
+      class="col-md-6 creation-type-container"
+      color=""
+      :outlined=true
+      :href="link"
+      @click="$emit('typeClick',2,type)"
+    >
+      <div class="row no-gutters" style="height: 100px">
+        <div class="flex-grow-0 flex-shrink-0 d-flex justify-center col col-3" style="">
+          <div class="icon align-self-center">
+            <i :class="icon" aria-hidden="true"></i>
+          </div>
+        </div>
+        <div class="infos-container flex-grow-1 flex-shrink-1 col col-9" style="">
+          <h4>{{ $t(title) }}</h4>
+          <p>
+            {{ $t(textContent) }}
+          </p>
+        </div>
+      </div>
+    </v-card>
+
+</template>
+
+<script lang="ts">
+import {defineComponent} from '@nuxtjs/composition-api'
+
+export default defineComponent({
+  props: {
+    title: {
+      type: String,
+      required: true
+    },
+    textContent: {
+      type: String,
+      required: true
+    },
+    icon: {
+      type: String,
+      required: true
+    },
+    link: {
+      type: String,
+      required: false
+    },
+    type: {
+      type: String,
+      required: false
+    }
+  }
+})
+</script>
+<style lang="scss" scoped>
+.creation-type-container{
+  border: none!important;
+  .icon{
+    i{
+      font-size: 50px;
+      color: var(--v-ot_grey-base, #777777);
+    }
+  }
+  .infos-container{
+    padding: 15px 0;
+    h4{
+      font-size: 15px;
+      color: var(--v-ot_green-base, #00AD8E);
+      font-weight: bold;
+      margin-bottom: 6px;
+    }
+    p{
+      font-size: 13px;
+      padding: 0;
+      margin: 0;
+      color: #767676;
+    }
+  }
+  &>div{
+    &:hover{
+      cursor: pointer;
+      background: var(--v-ot_light_green-base, #a9e0d6);
+    }
+  }
+
+}
+</style>

+ 52 - 0
components/Layout/LoadingScreen.vue

@@ -0,0 +1,52 @@
+<!-- Animation circulaire à afficher durant les chargements -->
+
+<template>
+  <v-overlay :value="loading" class="loading-page">
+    <v-progress-circular
+      indeterminate
+      size="64"
+    />
+  </v-overlay>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref, Ref } from '@nuxtjs/composition-api'
+
+export default defineComponent({
+  setup () {
+    const loading: Ref<boolean> = ref(false)
+
+    const set = (_num: number) => {
+      loading.value = true
+    }
+    const start = () => {
+      loading.value = true
+    }
+    const finish = () => {
+      loading.value = false
+    }
+    const fail = () => {
+      loading.value = false
+    }
+
+    return {
+      loading,
+      start,
+      finish,
+      fail,
+      set
+    }
+  }
+})
+</script>
+
+<style scoped>
+  .loading-page {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 1001!important;
+  }
+</style>

+ 152 - 0
components/Layout/Menu.vue

@@ -0,0 +1,152 @@
+<!--
+Menu principal de l'application
+Prend en paramètre une liste de ItemMenu et les met en forme
+-->
+
+<template>
+  <v-navigation-drawer
+    :mini-variant.sync="miniVariant"
+    v-model="open"
+    fixed
+    clipped
+    class="ot_dark_grey ot_menu_color--text"
+    app
+  >
+    <template #prepend>
+      <slot name="title"></slot>
+    </template>
+
+    <v-list class="left-menu">
+      <div v-for="(item, i) in menu" :key="i">
+        <v-list-item
+          v-if="!item.children"
+          :href="item.isExternalLink ? item.to : undefined"
+          :to="!item.isExternalLink ? item.to : undefined"
+          router
+          exact
+        >
+          <v-list-item-action>
+            <v-icon class="ot_menu_color--text" small>
+              {{ item.icon.name }}
+            </v-icon>
+          </v-list-item-action>
+          <v-list-item-content>
+            <v-list-item-title class="ot_menu_color--text" v-text="item.title" />
+          </v-list-item-content>
+        </v-list-item>
+
+        <v-list-group
+          v-else
+          v-model="item.active"
+          no-action
+        >
+          <template #activator>
+            <v-list-item-action>
+              <v-icon class="ot_menu_color--text" small>
+                {{ item.icon.name }}
+              </v-icon>
+            </v-list-item-action>
+            <v-list-item-content>
+              <v-list-item-title class="ot_menu_color--text" v-text="item.title" />
+            </v-list-item-content>
+          </template>
+
+          <v-list-item
+            v-for="child in item.children"
+            :key="child.title"
+            :href="child.isExternalLink ? child.to : undefined"
+            :to="!child.isExternalLink ? child.to : undefined"
+            router
+            exact
+          >
+            <v-list-item-action>
+              <v-icon class="ot_white--text" small>
+                {{ child.icon.name }}
+              </v-icon>
+            </v-list-item-action>
+            <v-list-item-content>
+              <v-list-item-title class="ot_white--text" v-text="child.title" />
+            </v-list-item-content>
+          </v-list-item>
+
+          <template #appendIcon>
+            <v-icon class="ot_menu_color--text" small>mdi-chevron-down</v-icon>
+          </template>
+        </v-list-group>
+      </div>
+    </v-list>
+
+    <template #append>
+      <slot name="foot"></slot>
+    </template>
+  </v-navigation-drawer>
+
+</template>
+
+<script setup lang="ts">
+import { ItemsMenu } from '~/types/interfaces'
+
+const props = defineProps({
+  menu: {
+    type: Array as () => ItemsMenu,
+    required: true
+  },
+  miniVariant: {
+    type: Boolean,
+    required: true
+  },
+  openMenu: {
+    type: Boolean,
+    required: true
+  }
+})
+
+const {openMenu} = toRefs(props)
+const open = ref(true)
+
+//Par défaut si l'écran est trop petit au chargement de la page, le menu doit être fermé.
+if(process.client)
+  open.value = window.innerWidth >= 1264
+
+const unwatch: WatchStopHandle = watch(openMenu, (newValue, oldValue) => {
+  if(newValue !== oldValue)
+    open.value = true
+})
+onUnmounted(() => {
+  unwatch()
+})
+</script>
+
+<style scoped>
+  .v-list-item__action, .v-list-group__header__prepend-icon {
+    margin-right: 10px !important;
+  }
+
+  .v-application--is-ltr .v-list-group--no-action > .v-list-group__header{
+    margin-left: 0px;
+    padding-left: 0px;
+  }
+  .v-application--is-ltr .v-list-group--no-action > .v-list-group__items > .v-list-item {
+    padding-left: 30px;
+  }
+
+  .v-list-item__title {
+    font-size: 14px;
+  }
+
+  .v-list-item {
+    min-height: 10px !important;
+  }
+
+  .v-list-item__action {
+    margin: 10px 0;
+  }
+
+  .v-list-item__content {
+    padding: 8px 0;
+  }
+
+  .home_menu {
+    font-size: 23px;
+  }
+</style>

+ 62 - 0
components/Layout/SubHeader/ActivityYear.vue

@@ -0,0 +1,62 @@
+<template>
+  <main class="d-flex">
+    <span class="mr-2 ot_dark_grey--text font-weight-bold">{{ $t(label) }} : </span>
+    <UiXeditableText
+      class="activity-year-input"
+      type="number"
+      :data="activityYear"
+      @update="updateActivityYear"
+    >
+      <template #xeditable.read="{inputValue}">
+        <v-icon aria-hidden="false" class="ot_green--text" x-small>
+          fas fa-edit
+        </v-icon>
+        <strong class="ot_green--text"> {{ inputValue }} <span v-if="yearPlusOne">/ {{ parseInt(inputValue) + 1 }}</span></strong>
+      </template>
+    </UiXeditableText>
+  </main>
+</template>
+
+<script lang="ts">
+import { defineComponent, useContext } from '@nuxtjs/composition-api'
+import { useMyProfile } from '~/composables/data/useMyProfile'
+import { $organizationProfile } from '~/services/profile/organizationProfile'
+import {useForm} from "~/composables/form/useForm";
+
+export default defineComponent({
+  setup () {
+    const { store, $dataPersister } = useContext()
+    const { updateMyProfile, setActivityYear, activityYear } = useMyProfile($dataPersister, store)
+    const { markAsNotDirty } = useForm(store)
+
+    const organizationProfile = $organizationProfile(store)
+
+    const yearPlusOne:boolean = !organizationProfile.isManagerProduct()
+    const label:string = organizationProfile.isSchool() ? 'schooling_year' : organizationProfile.isArtist() ? 'season_year' : 'cotisation_year'
+
+    const updateActivityYear = async (newDate:number) => {
+      markAsNotDirty()
+      setActivityYear(newDate)
+      await updateMyProfile()
+      window.location.reload()
+    }
+
+    return {
+      activityYear,
+      label,
+      yearPlusOne,
+      updateActivityYear
+    }
+  }
+})
+</script>
+
+<style lang="scss">
+  .activity-year-input{
+    max-height: 20px;
+    input{
+      font-size: 14px;
+      width: 55px !important;
+    }
+  }
+</style>

+ 48 - 0
components/Layout/SubHeader/Breadcrumbs.vue

@@ -0,0 +1,48 @@
+<template>
+  <v-breadcrumbs
+    :items="items"
+  />
+</template>
+
+<script lang="ts">
+import { defineComponent, computed, useContext, useRouter, ComputedRef } from '@nuxtjs/composition-api'
+import { AnyJson } from '~/types/interfaces'
+
+export default defineComponent({
+  setup () {
+    const { route, $config, app: { i18n } } = useContext()
+    const $router = useRouter()
+    const homeUrl: string = $config.baseURL_adminLegacy
+
+    const items: ComputedRef<Array<AnyJson>> = computed(() => {
+      const crumbs:Array<AnyJson> = []
+      crumbs.push({
+        text: i18n.t('welcome'),
+        href: homeUrl
+      })
+
+      const fullPath:string = route.value.path
+      const params:Array<string> = fullPath.startsWith('/') ? fullPath.substring(1).split('/') : fullPath.split('/')
+      let path:string = ''
+      params.forEach((param) => {
+        path = `${path}/${param}`
+        const match = $router.match(path)
+        if (match.name !== null) {
+          crumbs.push({
+            text: !parseInt(param, 10) ? i18n.t(param + '_breadcrumbs') : i18n.t('item'),
+            nuxt: true,
+            'exact-path': true,
+            to: path
+          })
+        }
+      })
+
+      return crumbs
+    })
+
+    return {
+      items
+    }
+  }
+})
+</script>

+ 93 - 0
components/Layout/SubHeader/DataTiming.vue

@@ -0,0 +1,93 @@
+<template>
+  <main class="d-flex align-baseline">
+    <span class="mr-2 ot_dark_grey--text font-weight-bold">{{ $t('display_data') }} : </span>
+
+    <v-btn-toggle
+      v-model="historicalBtn"
+      dense
+      class="ot_light_grey toggle-btn"
+      active-class="ot_green ot_white--text"
+      multiple
+    >
+      <v-btn max-height="25" class="font-weight-normal text-caption">
+        {{ $t('past') }}
+      </v-btn>
+
+      <v-btn max-height="25" class="font-weight-normal text-caption">
+        {{ $t('present') }}
+      </v-btn>
+
+      <v-btn max-height="25" class="font-weight-normal text-caption">
+        {{ $t('future') }}
+      </v-btn>
+    </v-btn-toggle>
+  </main>
+</template>
+
+<script lang="ts">
+import {defineComponent, onUnmounted, ref, watch, Ref, WatchStopHandle, useContext} from '@nuxtjs/composition-api'
+import {useForm} from "~/composables/form/useForm";
+import { useMyProfile } from '~/composables/data/useMyProfile'
+
+export default defineComponent({
+  setup () {
+    const { store, $dataPersister } = useContext()
+    const { markAsNotDirty } = useForm(store)
+    const { updateMyProfile, setHistorical, historical } = useMyProfile($dataPersister, store)
+
+    const historicalBtn: Ref<Array<number>> = initHistoricalBtn(historical.value as Array<any>)
+
+    const unwatch: WatchStopHandle = watch(historicalBtn, async (newValue) => {
+      const historicalChoice: Array<string> = initHistoricalChoice(newValue)
+
+      setHistorical(historicalChoice)
+      markAsNotDirty()
+      await updateMyProfile()
+      window.location.reload()
+    })
+
+    onUnmounted(() => {
+      unwatch()
+    })
+
+    return {
+      historicalBtn
+    }
+  }
+})
+
+/**
+   * Prépare le tableau de valeur numéraire devant être passé au component v-btn-toggle
+   * @param historical
+   */
+function initHistoricalBtn (historical: Array<any>) {
+  const timeChoice:Ref<Array<number>> = ref(Array<number>())
+  const historicalArray:Array<any> = ['past', 'present', 'future']
+
+  for (const key in historicalArray) {
+    if (historical[historicalArray[key]]) { timeChoice.value.push(parseInt(key)) }
+  }
+  return timeChoice
+}
+
+/**
+   * Transforme le résultat renvoyé par le component v-btn-toggle pour l'enregistrer coté AccessProfile
+   * @param historical
+   */
+function initHistoricalChoice (historical:Array<any>) {
+  const historicalArray:Array<any> = ['past', 'present', 'future']
+
+  const historicalChoice:Array<string> = []
+  for (const key in historical) {
+    historicalChoice.push(historicalArray[historical[key]])
+  }
+  return historicalChoice
+}
+</script>
+
+<style scoped lang="scss">
+  .toggle-btn{
+    z-index: 1;
+    border-radius: 4px 0px 0px 4px;
+  }
+</style>

+ 108 - 0
components/Layout/SubHeader/DataTimingRange.vue

@@ -0,0 +1,108 @@
+<template>
+  <main class="d-flex align-baseline">
+    <div v-if="show" class="d-flex align-baseline">
+      <span class="mr-2 ot_dark_grey--text font-weight-bold">{{ $t('period_choose') }}</span>
+      <UiInputDatePicker
+        class="time-range"
+        label="date_choose"
+        :data="datesRange"
+        :range="true"
+        :dense="true"
+        :single-line="true"
+        @update="updateDateTimeRange"
+      />
+    </div>
+
+    <v-tooltip bottom>
+      <template #activator="{ on, attrs }">
+        <v-btn
+          class="time-btn"
+          max-height="25"
+          v-bind="attrs"
+          elevation="0"
+          max-width="10px"
+          min-width="10px"
+          v-on="on"
+          @click="show=!show"
+        >
+          <v-icon color="ot_grey" class="font-weight-normal" x-small>
+            fas fa-history
+          </v-icon>
+        </v-btn>
+      </template>
+      <span>{{ $t('history_help') }}</span>
+    </v-tooltip>
+  </main>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent, onUnmounted, ref, watch, computed, ComputedRef, Ref, WatchStopHandle, useContext
+} from '@nuxtjs/composition-api'
+import { useMyProfile } from '~/composables/data/useMyProfile'
+import {useForm} from "~/composables/form/useForm";
+
+export default defineComponent({
+  setup (_, { emit }) {
+    const { store, $dataPersister } = useContext()
+    const { markAsNotDirty } = useForm(store)
+    const { updateMyProfile, setHistoricalRange, historical } = useMyProfile($dataPersister, store)
+
+    const datesRange:ComputedRef<Array<any>> = computed(() => {
+      return [historical.value.dateStart, historical.value.dateEnd]
+    })
+
+    const show:Ref<boolean> = ref(false)
+    if (historical.value.dateStart || historical.value.dateEnd) {
+      show.value = true
+      emit('showDateTimeRange', true)
+    }
+
+    const unwatch:WatchStopHandle = watch(show, (newValue) => {
+      emit('showDateTimeRange', newValue)
+    })
+
+    onUnmounted(() => {
+      unwatch()
+    })
+
+    const updateDateTimeRange = async (dates:Array<string>): Promise<any> => {
+      setHistoricalRange(dates)
+      markAsNotDirty()
+      await updateMyProfile()
+      window.location.reload()
+    }
+
+    return {
+      show,
+      datesRange,
+      updateDateTimeRange
+    }
+  }
+})
+</script>
+
+<style lang="scss">
+  .v-btn--active .v-icon{
+    color: #FFF !important;
+  }
+  .time-btn{
+    border-width: 1px 1px 1px 0px;
+    border-style: solid;
+    border-color: rgba(0, 0, 0, 0.12) !important;
+  }
+  .time-range{
+    max-height: 20px;
+    .v-text-field{
+      padding-top: 0 !important;
+      margin-top: 0 !important;
+    }
+    .v-icon{
+      font-size: 20px;
+    }
+    input{
+      font-size: 14px;
+      width: 400px !important;
+    }
+  }
+</style>

+ 117 - 0
components/Layout/SubHeader/PersonnalizedList.vue

@@ -0,0 +1,117 @@
+<template>
+  <main>
+    <v-menu
+      bottom
+      left
+      transition="slide-y-transition"
+      :close-on-content-click="false"
+      min-width="500"
+    >
+      <template #activator="{ on, attrs }">
+        <span
+          v-bind="attrs"
+          class="ot_green--text"
+          v-on="on"
+        >
+          {{ $t('my_list') }}
+        </span>
+      </template>
+      <v-card scrollable>
+        <v-card-title class="text-body-2 header-personnalized">
+          <v-text-field
+            dense
+            clear-icon="header-personnalized"
+            :loading="fetchState.pending"
+            v-model="search"
+            :label="$t('searchList')"
+          />
+        </v-card-title>
+        <v-card-text class="ma-0 pa-0 mt-n3 header_menu">
+          <v-list dense :subheader="true">
+            <template v-for="(item, index) in filteredItems">
+              <v-list-item
+                :id="item.id"
+                :key="index"
+                :href="goOn(item)"
+                router
+                exact
+              >
+                <v-list-item-title>
+                  {{item.label}} - <strong>{{item.menuKey}}</strong>
+                </v-list-item-title>
+              </v-list-item>
+            </template>
+
+          </v-list>
+        </v-card-text>
+      </v-card>
+
+    </v-menu>
+  </main>
+</template>
+
+<script lang="ts">
+import {
+  computed, defineComponent, useContext, useFetch, ref, Ref, ComputedRef
+} from '@nuxtjs/composition-api'
+import { Collection } from '@vuex-orm/core'
+import { QUERY_TYPE } from '~/types/enums'
+import { PersonalizedList } from '~/models/Access/PersonalizedList'
+import { repositoryHelper } from '~/services/store/repository'
+import { AnyJson } from '~/types/interfaces'
+import {queryHelper} from "~/services/store/query";
+
+export default defineComponent({
+  fetchOnServer: false,
+  setup () {
+    const { $dataProvider, $config, app:{i18n} } = useContext()
+    const homeUrl:string = $config.baseURL_adminLegacy
+
+    const {fetchState} = useFetch(async () => {
+      await $dataProvider.invoke({
+        type: QUERY_TYPE.MODEL,
+        model: PersonalizedList
+      })
+    })
+
+    const items:ComputedRef<Array<AnyJson>> = computed(() => {
+      const query = repositoryHelper.getRepository(PersonalizedList).query()
+      const lists = queryHelper.getCollection(query, {'label':'asc'}) as Collection<PersonalizedList>
+      lists.map(item => {
+        const menu: string = i18n.t(item.menuKey) as string
+        item.menuKey = menu
+      })
+      return lists
+    })
+
+    const search = ref('');
+    const filteredItems = computed(() => {
+      return items.value.filter( item => {
+          return item.label.toLowerCase().indexOf(search.value.toLowerCase()) >= 0
+            ||item.menuKey.toLowerCase().indexOf(search.value.toLowerCase()) >= 0
+        }
+      )
+    })
+
+    const goOn = (list:PersonalizedList) => {
+      return `${homeUrl}/${list.entity}/list/${list.id}`
+    }
+
+    return {
+      items,
+      fetchState,
+      goOn,
+      filteredItems,
+      search,
+      homeUrl
+    }
+  }
+})
+</script>
+
+<style type="text/css" lang="scss">
+  .header-personnalized{
+    margin-bottom: 0px;
+    padding-bottom: 0px;
+  }
+</style>

+ 51 - 0
components/Layout/Subheader.vue

@@ -0,0 +1,51 @@
+<!--
+Second header de l'application
+Contient entre autres le breadcrumb, les commandes de changement d'année et les listes personnalisées
+-->
+
+<template>
+  <main>
+    <v-card
+      class="d-md-flex ot_light_grey text-body-2"
+      flat
+      tile
+    >
+      <LayoutSubHeaderBreadcrumbs class="mr-auto d-sm-none d-md-flex d-none d-sm-flex" />
+
+      <v-card
+        class="d-md-flex ot_light_grey pt-2 mr-6  align-baseline"
+        flat
+        tile
+      >
+        <LayoutSubHeaderActivityYear v-if="!showDateTimeRange" class="activity-year" />
+        <div v-if="hasMenuOrIsTeacher" class="d-sm-none d-md-flex d-none d-sm-flex">
+          <LayoutSubHeaderDataTiming v-if="!showDateTimeRange" class="data-timing ml-2" />
+          <LayoutSubHeaderDataTimingRange class="data-timing-range ml-n1" @showDateTimeRange="showDateTimeRange=$event" />
+          <LayoutSubHeaderPersonnalizedList class="personalized-list ml-2" />
+        </div>
+      </v-card>
+    </v-card>
+  </main>
+</template>
+
+<script lang="ts">
+import {defineComponent, ref, Ref, useContext} from '@nuxtjs/composition-api'
+import {UseAccess} from "~/composables/utils/useAccess";
+
+export default defineComponent({
+  setup () {
+    const {store} = useContext()
+    const {hasMenuOrIsTeacher} = UseAccess(store)
+    const showDateTimeRange: Ref<boolean> = ref(false)
+
+    return {
+      showDateTimeRange,
+      hasMenuOrIsTeacher
+    }
+  }
+})
+</script>
+
+<style scoped>
+
+</style>

+ 0 - 12
components/UserBlock.vue

@@ -1,12 +0,0 @@
-<template>
-  <p>User Name : {{user.name}}</p>
-</template>
-
-<script setup lang="ts">
-import {useRepo} from "pinia-orm";
-import User from "~/models/User";
-
-
-const user = useRepo(User).find(20840)
-
-</script>

+ 66 - 0
composables/layout/Menus/accessMenu.ts

@@ -0,0 +1,66 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import { $organizationProfile } from '~/services/profile/organizationProfile'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class AccessMenu
+ * Classe pour la construction du Menu Répertoire
+ */
+class AccessMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+    super($config)
+  }
+
+  /**
+   * Construit le menu Répertoire, ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const children: ItemsMenu = []
+    const {can} = useAbility()
+    
+    if (can('display', 'accesses_page')) {
+      const organization = $organizationProfile()
+      const to = organization.isSchool() ? '/students/list/' : '/adherent/list/'
+      children.push(this.constructMenu('person', {name: 'fa-user'}, to, true))
+    }
+
+    if (can('display', 'student_registration_page')) {
+      children.push(this.constructMenu('family_view', {name: 'fa-users'}, '/student_registration/new', true))
+    }
+
+    if (can('display', 'education_student_next_year_page')) {
+      children.push(this.constructMenu('education_student_next_year', {name: 'fa-list-alt'}, '/education_student_next_year/list/', true))
+    }
+
+    if (can('display', 'commissions_page')) {
+      children.push(this.constructMenu('commissions', {name: 'fa-street-view'}, '/commissions/list/', true))
+    }
+
+    if (can('display', 'network_children_page')) {
+      children.push(this.constructMenu('network', {name: 'fa-sitemap'}, 'networks/list/', true))
+    }
+
+    if (can('display', 'network_parents_page')) {
+      children.push(this.constructMenu('my_network', {name: 'fa-sitemap'}, '/network_artist_schools/list/', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    } else if (children.length > 0) {
+      return this.constructMenu('address_book', {name: 'fa-address-book'}, undefined, undefined, children)
+    } else {
+      return null
+    }
+  }
+}
+
+export const getAccessMenu = () => new AccessMenu().getMenu()

+ 106 - 0
composables/layout/Menus/accountMenu.ts

@@ -0,0 +1,106 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useProfileAccessStore} from "~/store/profile/access";
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class AccountMenu
+ * Classe pour la construction du Menu Mon compte
+ */
+class AccountMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+    super($config)
+  }
+
+  /**
+   * Construit le menu Header Configuration ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getHeaderMenu (): ItemMenu | null {
+    const profileAccessStore = useProfileAccessStore();
+    const {can} = useAbility()
+
+    const children: ItemsMenu = []
+
+    if (can('display', 'my_schedule_page')) {
+      children.push(this.constructMenu('my_schedule_page', undefined, '/my_calendar', true))
+    }
+
+    if (can('display', 'attendance_bookings_page')) {
+      children.push(this.constructMenu('attendance_bookings_menu', undefined, '/own_attendance', true))
+    }
+
+    if (can('display', 'my_attendance_page')) {
+      children.push(this.constructMenu('my_attendance', undefined, '/my_attendances/list/', true))
+    }
+
+    if (can('display', 'my_invitation_page')) {
+      children.push(this.constructMenu('my_invitation', undefined, '/my_invitations/list/', true))
+    }
+
+    if (can('display', 'my_students_page')) {
+      children.push(this.constructMenu('my_students', undefined, '/my_students/list/', true))
+    }
+
+    if (can('display', 'my_students_education_students_page')) {
+      children.push(this.constructMenu('my_students_education_students', undefined, '/my_students_education_students/list/', true))
+    }
+
+    if (can('display', 'criteria_notations_page') || can('display', 'criteria_notations_page_from_account_menu')) {
+      children.push(this.constructMenu('criteria_notations', undefined, '/criteria_notations/list/', true))
+    }
+
+    if (can('display', 'my_education_students_page')) {
+      children.push(this.constructMenu('my_education_students', undefined, `/main/my_profile/${profileAccessStore.id}/dashboard/my_education_students/list/`, true))
+    }
+
+    if (can('display', 'send_an_email_page')) {
+      children.push(this.constructMenu('send_an_email', undefined, `/list/create/emails`, true))
+    }
+
+    if (can('display', 'my_documents_page')) {
+      children.push(this.constructMenu('my_documents', undefined, `/main/my_profile/${profileAccessStore.id}/dashboard/show/my_access_file`, true))
+    }
+
+    if (can('display', 'my_profile_page')) {
+      children.push(this.constructMenu('my_profile', undefined, `/main/my_profile/${profileAccessStore.id}/dashboard`, true))
+    }
+
+    if (can('display', 'adherent_list_page')) {
+      children.push(this.constructMenu('adherent_list', undefined, `/adherent_contacts/list/`, true))
+    }
+
+    if (can('display', 'subscription_page')) {
+      children.push(this.constructMenu('my_subscription', undefined, `/subscription`))
+    }
+
+    if (can('display', 'my_bills_page')) {
+      children.push(this.constructMenu('my_bills', undefined, `/main/my_profile/${profileAccessStore.id}/dashboard/show/my_bills`, true))
+    }
+
+    if (can('display', 'cmf_licence_person_page')) {
+      children.push(this.constructMenu('print_my_licence', undefined, `/licence_cmf/user`, true))
+    }
+
+    const accountMenu = this.constructMenu('my_account', {
+      avatarId: profileAccessStore.avatarId,
+      avatarByDefault: profileAccessStore.gender == 'MISTER' ? 'men-1.png' : 'women-1.png'
+    }, undefined, undefined, children, false)
+
+    const actions: ItemsMenu = [];
+    actions.push(this.constructMenu('logout', undefined, `/logout`, true))
+
+    accountMenu.actions = actions
+
+    return accountMenu
+  }
+}
+
+export const getAccountMenu = () => new AccountMenu().getHeaderMenu()

+ 69 - 0
composables/layout/Menus/admin2iosMenu.ts

@@ -0,0 +1,69 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class Admin2iosMenu
+ * Classe pour la construction du Menu Admin 2IOS
+ */
+class Admin2iosMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issus du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Administration 2ios ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+
+    const children: ItemsMenu = []
+
+    if (can('display', 'all_accesses_page')) {
+      children.push(this.constructMenu('all_accesses', {name: 'fa-users'}, '/all_accesses/list/', true))
+    }
+
+    if (can('display', 'all_organizations_page')) {
+      children.push(this.constructMenu('all_organizations', {name: 'fa-building'}, '/organization_params/list/', true))
+    }
+
+    if (can('display', 'tips_page')) {
+      children.push(this.constructMenu('tips', {name: 'fa-info-circle'}, '/tips/list/', true))
+    }
+
+    if (can('display', 'dgv_page')) {
+      children.push(this.constructMenu('dgv', {name: 'fa-house-damage'}, '/admin2ios/dgv', true))
+    }
+
+    if (can('display', 'cmf_cotisation_page')) {
+      children.push(this.constructMenu('cmf_cotisation', {name: 'fa-info-circle'}, '/admin2ios/cotisationcmf', true))
+    }
+
+    if (can('display', 'right_page')) {
+      children.push(this.constructMenu('right_menu', {name: 'fa-balance-scale-right'}, '/admin2ios/right', true))
+    }
+
+    if (can('display', 'tree_page')) {
+      children.push(this.constructMenu('tree_menu', {name: 'fa-sitemap'}, '/admin2ios/tree', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    } else if (children.length > 0) {
+      return this.constructMenu('admin2ios', {name: 'fa-sitemap'}, undefined, undefined, children)
+    } else {
+      return null
+    }
+  }
+}
+
+export const getAdmin2iosMenu = () => new Admin2iosMenu().getMenu()

+ 45 - 0
composables/layout/Menus/agendaMenu.ts

@@ -0,0 +1,45 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class AgendaMenu
+ * Classe pour la construction du Menu agenda
+ */
+class AgendaMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Agenda ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    const children: ItemsMenu = []
+
+    if (can('display', 'agenda_page')) {
+      children.push(this.constructMenu('schedule', {name: 'fa-calendar-alt'}, '/calendar', true))
+    }
+
+    if (can('display', 'attendance_page')) {
+      children.push(this.constructMenu('attendances', {name: 'fa-calendar-check'}, '/attendances/list/', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    }
+    return children.length > 0 ? this.constructMenu('schedule', {name: 'fa-calendar-alt'}, undefined, undefined, children) : null
+  }
+}
+
+export const getAgendaMenu = () => new AgendaMenu().getMenu()

+ 56 - 0
composables/layout/Menus/baseMenu.ts

@@ -0,0 +1,56 @@
+import {NuxtConfig} from "nuxt";
+import {ItemMenu, ItemsMenu, IconItem} from '~/types/interfaces'
+
+/**
+ * @category composables/layout/Menus
+ * @class BaseMenu
+ * Classe abstraite pour chacun des menu
+ */
+class BaseMenu {
+  protected $config: NuxtConfig;
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor ($config: NuxtConfig) {
+    this.$config = $config
+  }
+
+  /**
+   * Construit un ItemMenu
+   * @param {IconItem} icon
+   * @param {string} title titre qui sera traduit
+   * @param {string} link lien
+   * @param {boolean} isOldLink est-ce un lien renvoyant vers l'ancien admin?
+   * @param {Array<ItemMenu>} children Tableau d'ItemMenu représentant les sous menu du menu principal
+   * @param {boolean} isExternalLink est-ce un lien renvoyant vers l'extérieur?
+   * @param {Array<ItemMenu>} actions Tableau d'ItemMenu représentant les actions devant apparaitre en bas du menu
+   * @return {ItemMenu}
+   */
+  constructMenu (title: string, icon?: IconItem, link?: string, isOldLink?: boolean, children?: Array<ItemMenu>, isExternalLink?: boolean): ItemMenu {
+    return children ? {
+      title,
+      icon,
+      children
+    } : {
+      icon,
+      title,
+      to: `${isExternalLink || !isOldLink ? '' : this.$config.baseURL_adminLegacy}${link}`,
+      isExternalLink: isExternalLink || isOldLink
+    }
+  }
+
+  getMenu (): ItemMenu | null {
+    return null
+  }
+
+  getMenus (): ItemsMenu | null {
+    return null
+  }
+
+  getHeaderMenu (): ItemMenu | null {
+    return null
+  }
+}
+export default BaseMenu

+ 72 - 0
composables/layout/Menus/billingMenu.ts

@@ -0,0 +1,72 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class BillingMenu
+ * Classe pour la construction du Menu Facturation
+ */
+class BillingMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Facturation ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    const children: ItemsMenu = []
+
+    if (can('display', 'billing_product_page')) {
+      children.push(this.constructMenu('billing_product', {name: 'fa-cube'}, '/intangibles/list/', true))
+    }
+
+    if (can('display', 'billing_products_by_student_page')) {
+      children.push(this.constructMenu('billing_products_by_student', {name: 'fa-cubes'}, '/access_intangibles/list/', true))
+    }
+
+    if (can('display', 'billing_edition_page')) {
+      children.push(this.constructMenu('billing_edition', {name: 'fa-copy'}, '/billing_edition', true))
+    }
+
+    if (can('display', 'billing_accounting_page')) {
+      children.push(this.constructMenu('billing_accounting', {name: 'fa-file-alt'}, '/bill_accountings/list/', true))
+    }
+
+    if (can('display', 'billing_payment_list_page')) {
+      children.push(this.constructMenu('billing_payment_list', {name: 'fa-credit-card'}, '/bill_payments_list/list/', true))
+    }
+
+    if (can('display', 'pes_page')) {
+      children.push(this.constructMenu('pes_export', {name: 'fa-align-justify'}, '/pes/list/', true))
+    }
+
+    if (can('display', 'berger_levrault_page')) {
+      children.push(this.constructMenu('berger_levrault_export', {name: 'fa-align-justify'}, '/berger_levraults/list/', true))
+    }
+
+    if (can('display', 'jvs_page')) {
+      children.push(this.constructMenu('jvs_export', {name: 'fa-align-justify'}, '/jvs/list/', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    } else if (children.length > 0) {
+      return this.constructMenu('billing', {name: 'fa-euro-sign'}, undefined, undefined, children)
+    } else {
+      return null
+    }
+  }
+}
+
+export const getBillingMenu = () => new BillingMenu().getMenu()

+ 52 - 0
composables/layout/Menus/communicationMenu.ts

@@ -0,0 +1,52 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class CommunicationMenu
+ * Classe pour la construction du Menu Communication
+ */
+class CommunicationMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Communication ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    const children: ItemsMenu = []
+
+    if (can('display', 'inbox_page')) {
+      children.push(this.constructMenu('inbox', {name: 'fa-inbox'}, '/messages/list/', true))
+    }
+
+    if (can('display', 'message_send_page')) {
+      children.push(this.constructMenu('message_send', {name: 'fa-paper-plane'}, '/messagessends/list/', true))
+    }
+
+    if (can('display', 'message_templates_page')) {
+      children.push(this.constructMenu('message_templates', {name: 'fa-edit'}, '/templates/list/', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    } else if (children.length > 0) {
+      return this.constructMenu('communication', {name: 'fa-comments'}, undefined, undefined, children)
+    } else {
+      return null
+    }
+  }
+}
+
+export const getCommunicationMenu = () => new CommunicationMenu().getMenu()

+ 95 - 0
composables/layout/Menus/configurationMenu.ts

@@ -0,0 +1,95 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useProfileOrganizationStore} from "~/store/profile/organization";
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class ConfigurationMenu
+ * Classe pour la construction du Menu Paramètres
+ */
+class ConfigurationMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+    super($config)
+  }
+
+  /**
+   * Construit le menu Header Configuration ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getHeaderMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    const profileOrganizationStore = useProfileOrganizationStore()
+    const children: ItemsMenu = []
+
+    if (can('display', 'organization_page')) {
+      // children.push(this.constructMenu('organization_page', undefined, `/main/organizations/${profileOrganizationStore.id}/dashboard`, true))
+      children.push(this.constructMenu('organization_page', undefined, `/organization`, false))
+    }
+
+    if (can('display', 'cmf_licence_page')) {
+      children.push(this.constructMenu('cmf_licence_generate', undefined, '/cmf_licence/organization'))
+    }
+
+    if (can('display', 'parameters_page')) {
+      // children.push(this.constructMenu('parameters', undefined,`/main/edit/parameters/${profileOrganizationStore.id}`, true))
+      children.push(this.constructMenu('parameters', undefined,`/parameters`, false))
+    }
+
+    if (can('display', 'place_page')) {
+      children.push(this.constructMenu('place', undefined, '/places/list/', true))
+    }
+
+    if (can('display', 'education_page')) {
+      children.push(this.constructMenu('education', undefined, '/educations/list/', true))
+    }
+
+    if (can('display', 'tag_page')) {
+      children.push(this.constructMenu('tag', undefined, '/taggs/list/', true))
+    }
+
+    if (can('display', 'activities_page')) {
+      children.push(this.constructMenu('activities', undefined, '/activities/list/', true))
+    }
+
+    if (can('display', 'template_systems_page')) {
+      children.push(this.constructMenu('template_systems', undefined,'/template_systems/list/', true))
+    }
+
+    if (can('display', 'billing_settings_page')) {
+      children.push(this.constructMenu('billing_settings', undefined, '/main/edit/billing_settings/' + profileOrganizationStore.id, true))
+    }
+
+    if (can('display', 'online_registration_settings_page')) {
+      children.push(this.constructMenu('online_registration_settings', undefined, '/main/edit/online_registration_settings/' + profileOrganizationStore.id, true))
+    }
+
+    if (can('display', 'transition_next_year_page')) {
+      children.push(this.constructMenu('transition_next_year', undefined, '/transition_next_year', true))
+    }
+
+    if (can('display', 'course_duplication_page')) {
+      children.push(this.constructMenu('course_duplication', undefined, '/duplicate_courses', true))
+    }
+
+    if (can('display', 'import_page')) {
+      children.push(this.constructMenu('import', undefined, '/import/all', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    } else if (children.length > 0) {
+      return this.constructMenu('configuration', {name: 'fa-cogs'}, undefined, undefined, children)
+    } else {
+      return null
+    }
+  }
+}
+
+export const getConfigurationMenu = () => new ConfigurationMenu().getHeaderMenu()

+ 108 - 0
composables/layout/Menus/cotisationsMenu.ts

@@ -0,0 +1,108 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class CotisationsMenu
+ * Classe pour la construction du Menu Cotisation (CMF)
+ */
+class CotisationsMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Cotisations ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    const children: ItemsMenu = []
+
+    if (can('display', 'rate_cotisation_page')) {
+      children.push(this.constructMenu('rate_cotisation', {name: 'fa-euro-sign'}, '/cotisation/rate', true))
+    }
+
+    if (can('display', 'parameters_cotisation_page')) {
+      children.push(this.constructMenu('parameters_cotisation', {name: 'fa-euro-sign'}, '/cotisation/parameter', true))
+    }
+
+    if (can('display', 'send_cotisation_page')) {
+      children.push(this.constructMenu('send_cotisation', {name: 'fa-euro-sign'}, '/cotisation/send', true))
+    }
+
+    if (can('display', 'state_cotisation_page')) {
+      children.push(this.constructMenu('state_cotisation', {name: 'fa-euro-sign'}, '/cotisation/state', true))
+    }
+
+    if (can('display', 'pay_cotisation_page')) {
+      children.push(this.constructMenu('pay_cotisation', {name: 'fa-euro-sign'}, '/cotisation/pay', true))
+    }
+
+    if (can('display', 'check_cotisation_page')) {
+      children.push(this.constructMenu('check_cotisation', {name: 'fa-euro-sign'}, '/cotisation/check', true))
+    }
+
+    if (can('display', 'ledger_cotisation_page')) {
+      children.push(this.constructMenu('ledger_cotisation', {name: 'fa-euro-sign'}, '/cotisation/ledger', true))
+    }
+
+    if (can('display', 'magazine_cotisation_page')) {
+      children.push(this.constructMenu('magazine_cotisation', {name: 'fa-euro-sign'}, '/cotisation/magazine', true))
+    }
+
+    if (can('display', 'ventilated_cotisation_page')) {
+      children.push(this.constructMenu('ventilated_cotisation', {name: 'fa-euro-sign'}, '/cotisation/ventilated', true))
+    }
+
+    if (can('display', 'pay_erase_cotisation_page')) {
+      children.push(this.constructMenu('pay_erase_cotisation', {name: 'fa-euro-sign'}, '/cotisation/payerase', true))
+    }
+
+    if (can('display', 'resume_cotisation_page')) {
+      children.push(this.constructMenu('resume_cotisation', {name: 'fa-euro-sign'}, '/cotisation/resume', true))
+    }
+
+    if (can('display', 'history_cotisation_page')) {
+      children.push(this.constructMenu('history_cotisation', {name: 'fa-euro-sign'}, '/cotisation/history', true))
+    }
+
+    if (can('display', 'call_cotisation_page')) {
+      children.push(this.constructMenu('call_cotisation', {name: 'fa-euro-sign'}, '/cotisation/call', true))
+    }
+
+    if (can('display', 'history_struture_cotisation_page')) {
+      children.push(this.constructMenu('history_struture_cotisation', {name: 'fa-euro-sign'}, '/cotisation/historystructure', true))
+    }
+
+    if (can('display', 'insurance_cotisation_page')) {
+      children.push(this.constructMenu('insurance_cotisation', {name: 'fa-euro-sign'}, '/cotisation/insurance', true))
+    }
+
+    if (can('display', 'resume_all_cotisation_page')) {
+      children.push(this.constructMenu('resume_all_cotisation', {name: 'fa-euro-sign'}, '/cotisation/resumeall', true))
+    }
+
+    if (can('display', 'resume_pay_cotisation_page')) {
+      children.push(this.constructMenu('resume_pay_cotisation', {name: 'fa-euro-sign'}, '/cotisation/resumepay', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    } else if (children.length > 0) {
+      return this.constructMenu('cotisations', {name: 'fa-money-bill'}, undefined, undefined, children)
+    } else {
+      return null
+    }
+  }
+}
+
+export const getCotisationsMenu = () => new CotisationsMenu().getMenu()

+ 36 - 0
composables/layout/Menus/donorsMenu.ts

@@ -0,0 +1,36 @@
+import { ItemMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class DonorsMenu
+ * Classe pour la construction du Menu Donneurs
+ */
+class DonorsMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Partenariat et Dons ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    
+    if (can('display', 'donors_page')) {
+      return this.constructMenu('donors', {name: 'far fa-handshake'}, '/donors/list/', true)
+    }
+    return null
+  }
+}
+
+export const getDonorsMenu = () => new DonorsMenu().getMenu()

+ 68 - 0
composables/layout/Menus/educationalMenu.ts

@@ -0,0 +1,68 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class EducationalMenu
+ * Classe pour la construction du Menu Suivi pédagogique
+ */
+class EducationalMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Suivi pédagogique ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    const children: ItemsMenu = []
+
+    if (can('display', 'criteria_notations_page')) {
+      children.push(this.constructMenu('criteria_notations', {name: 'fa-bars'}, '/criteria_notations/list/', true))
+    }
+
+    if (can('display', 'education_notation_config_page')) {
+      children.push(this.constructMenu('education_notation_configs', {name: 'fa-bars'},  '/education_notation_configs/list/', true))
+    }
+
+    if (can('display', 'seizure_period_page')) {
+      children.push(this.constructMenu('seizure_period', {name: 'fa-calendar-alt'}, '/education_teachers/list/', true))
+    }
+
+    if (can('display', 'test_seizure_page')) {
+      children.push(this.constructMenu('test_seizure', {name: 'fa-pencil-alt'}, '/education_input/list/', true))
+    }
+
+    if (can('display', 'test_validation_page')) {
+      children.push(this.constructMenu('test_validation', {name: 'fa-check'}, '/education_notations/list/', true))
+    }
+
+    if (can('display', 'examen_results_page')) {
+      children.push(this.constructMenu('examen_results', {name: 'fa-graduation-cap'}, '/examen_convocations/list/', true))
+    }
+
+    if (can('display', 'education_by_student_validation_page')) {
+      children.push(this.constructMenu('education_by_student_validation', {name: 'fa-check-square'}, '/education_by_student/list/', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    } else if (children.length > 0) {
+      return this.constructMenu('education_state', {name: 'fa-graduation-cap'}, undefined, undefined, children)
+    } else {
+      return null
+    }
+  }
+}
+
+export const getEducationalMenu = () => new EducationalMenu().getMenu()

+ 36 - 0
composables/layout/Menus/equipmentMenu.ts

@@ -0,0 +1,36 @@
+import { ItemMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class EquipmentMenu
+ * Classe pour la construction du Menu Matériel
+ */
+class EquipmentMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Equipement ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    
+    if (can('display', 'equipment_page')) {
+      return this.constructMenu('equipment', {name: 'fa-cube'}, '/equipment/list/', true)
+    }
+    return null
+  }
+}
+
+export const getEquipmentMenu = () => new EquipmentMenu().getMenu()

+ 36 - 0
composables/layout/Menus/medalsMenu.ts

@@ -0,0 +1,36 @@
+import { ItemMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class MedalsMenu
+ * Classe pour la construction du Menu Médailles
+ */
+class MedalsMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Médails et Dons ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    
+    if (can('display', 'medals_page')) {
+      return this.constructMenu('medals', {name: 'fa-trophy'}, '/medals/list/', true)
+    }
+    return null
+  }
+}
+
+export const getMedalsMenu = () => new MedalsMenu().getMenu()

+ 40 - 0
composables/layout/Menus/myAccessesMenu.ts

@@ -0,0 +1,40 @@
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import {useProfileAccessStore} from "~/store/profile/access";
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class MyAccessesMenu
+ * Classe pour la construction du Menu Mon profile
+ */
+class MyAccessesMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Header Multi compte ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getHeaderMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    const children: ItemsMenu = []
+
+    const profileAccessStore = useProfileAccessStore();
+    useEach(profileAccessStore.multiAccesses, (access) => {
+      children.push(this.constructMenu(access.name as string, undefined, '/switch/' + access.id, true))
+    })
+
+    return children.length > 0 ? this.constructMenu('multiAccesses', {name: 'fa-building'}, undefined, undefined, children) : null
+  }
+}
+
+export const getMyAccessesMenu = () => new MyAccessesMenu().getHeaderMenu()

+ 52 - 0
composables/layout/Menus/myFamilyMenu.ts

@@ -0,0 +1,52 @@
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import {useProfileAccessStore} from "~/store/profile/access";
+import {useProfileOrganizationStore} from "~/store/profile/organization";
+
+/**
+ * @category composables/layout/Menus
+ * @class MyFamilyMenu
+ * Classe pour la construction du Menu Famille
+ */
+class MyFamilyMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Header Changement d'utilisateur ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getHeaderMenu (): ItemMenu | null {
+    const children: ItemsMenu = []
+
+    // Si Access des membres de la familles (enfants)
+    const profileAccessStore = useProfileAccessStore()
+    const profileOrganizationStore = useProfileOrganizationStore()
+    useEach(profileAccessStore.familyAccesses, (access) => {
+      const url = `/switch_user/${profileOrganizationStore.id}/${profileAccessStore.id}/${access.id}`
+      children.push(this.constructMenu(`${access.givenName} ${access.name}`, {
+        avatarId: access.avatarId,
+        avatarByDefault: access.gender == 'MISTER' ? 'men-1.png' : 'women-1.png'
+      }, url, true))
+    })
+
+    // Si on est en compte swtich, on doit pouvoir retourner au compte d'origine
+    const originalAccess = profileAccessStore.originalAccess
+    if (originalAccess && !originalAccess.isSuperAdminAccess) {
+      const url = `/switch_user/${originalAccess.organization.id}/${originalAccess.id}/exit`
+      children.push(this.constructMenu(`${originalAccess.givenName} ${originalAccess.name}`, undefined, url, true))
+    }
+
+    return children.length > 0 ? this.constructMenu('familyAccesses', {name: 'fa-users'}, undefined, undefined, children) : null
+  }
+}
+
+export const getMyFamilyMenu = () => new MyFamilyMenu().getHeaderMenu()

+ 57 - 0
composables/layout/Menus/parametersMenu.ts

@@ -0,0 +1,57 @@
+import {ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class ConfigurationMenu
+ * Classe pour la construction du Menu Paramètres
+ */
+class ParametersMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Header Configuration ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenus (): ItemsMenu | null {
+    const {can} = useAbility()
+    const children: ItemsMenu = []
+
+    if (can('display', 'parameters_page')) {
+      children.push(this.constructMenu('general_params', {name: 'fa-cogs'},`/parameters`, false))
+    }
+    if (can('display', 'parameters_communication_page')) {
+      children.push(this.constructMenu('communication_params', {name: 'fa-comments'},`/parameters/communication`, false))
+    }
+    if (can('display', 'parameters_student_page')) {
+      children.push(this.constructMenu('students_params', {name: 'fa-users'},`/parameters/student`, false))
+    }
+    if (can('display', 'parameters_education_page')) {
+      children.push(this.constructMenu('education_params', {name: 'fa-graduation-cap'},`/parameters/education`, false))
+    }
+    if (can('display', 'parameters_bills_page')) {
+      children.push(this.constructMenu('bills_params', {name: 'fa-euro-sign'},`/parameters/billing`, false))
+    }
+    if (can('display', 'parameters_secure_page')) {
+      children.push(this.constructMenu('secure_params', {name: 'fa-lock'},`/parameters/secure`, false))
+    }
+
+    if (children.length > 0) {
+      return children
+    } else {
+      return null
+    }
+  }
+}
+
+export const getParametersMenu = () => new ParametersMenu().getMenus()

+ 56 - 0
composables/layout/Menus/statsMenu.ts

@@ -0,0 +1,56 @@
+import { ItemMenu, ItemsMenu, Menu } from '~/types/interfaces'
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import {useAbility} from "@casl/vue";
+
+/**
+ * @category composables/layout/Menus
+ * @class StatsMenu
+ * Classe pour la construction du Menu Statistiques
+ */
+class StatsMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Statistique et Dons ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const {can} = useAbility()
+    const children: ItemsMenu = []
+
+    if (can('display', 'report_activity_page')) {
+      children.push(this.constructMenu('report_activity', {name: 'fa-chart-bar'}, '/report_activity', true))
+    }
+
+    if (can('display', 'education_quotas_page')) {
+      children.push(this.constructMenu('educations_quotas_by_education', {name: 'fa-user-circle'},  '/educations_quotas_by_education_year/list/', true))
+    }
+
+    if (can('display', 'fede_stats_page')) {
+      children.push(this.constructMenu('fede_stats', {name: 'fa-chart-bar'}, '/statistic/membersfedeonly', true))
+    }
+
+    if (can('display', 'structure_stats_page')) {
+      children.push(this.constructMenu('structure_stats', {name: 'fa-chart-bar'}, '/statistic/membersfedeassos', true))
+    }
+
+    if (children.length === 1) {
+      return children[0]
+    } else if (children.length > 0) {
+      return this.constructMenu('stats', {name: 'fa-chart-bar'}, undefined, undefined, children)
+    } else {
+      return null
+    }
+  }
+}
+
+export const getStatsMenu = () => new StatsMenu().getMenu()

+ 62 - 0
composables/layout/Menus/websiteMenu.ts

@@ -0,0 +1,62 @@
+import BaseMenu from '~/composables/layout/Menus/baseMenu'
+import { ItemMenu, ItemsMenu, Menu, organizationState } from '~/types/interfaces'
+import {useProfileOrganizationStore} from "~/store/profile/organization";
+import {useProfileAccessStore} from "~/store/profile/access";
+
+/**
+ * @category composables/layout/Menus
+ * @class WebsiteMenu
+ * Classe pour la construction du Menu Sites internet
+ */
+class WebsiteMenu extends BaseMenu implements Menu {
+
+  /**
+   * @constructor
+   * Initialisation des services issues du context
+   */
+  constructor () {
+    const {$config} = useNuxtApp()
+
+    super($config)
+  }
+
+  /**
+   * Construit le menu Site internet ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getMenu (): ItemMenu | null {
+    const profileOrganizationStore = useProfileOrganizationStore()
+    const profileAccessStore = useProfileAccessStore()
+
+    if (!profileOrganizationStore.website && profileAccessStore.isAdminAccess) {
+      return this.constructMenu('advanced_modification', {name: 'fa-globe-americas'}, this.getWebsite(profileOrganizationStore) + '/typo3', false, undefined, true)
+    }
+    return null
+  }
+
+  /**
+   * Construit le menu Header des Sites internet ou null si aucune page accessible
+   * @return {ItemMenu | null}
+   */
+  getHeaderMenu (): ItemMenu | null {
+    const profileOrganizationStore = useProfileOrganizationStore()
+
+    const children: ItemsMenu = []
+
+    children.push(this.constructMenu(profileOrganizationStore.name as string, undefined, this.getWebsite(profileOrganizationStore), false, undefined, true))
+
+    useEach(profileOrganizationStore.parents, (parent:any) => {
+      if(parent.id != process.env.OPENTALENT_MANAGER_ID){
+        children.push(this.constructMenu(parent.name, undefined, this.getWebsite(parent), false, undefined, true))
+      }
+    })
+
+    return children.length > 0 ? this.constructMenu('website', {name: 'fa-globe-americas'}, undefined, undefined, children) : null
+  }
+
+  getWebsite (organization: organizationState): string {
+    return organization.website ?? '';
+  }
+}
+
+export const getWebsiteMenu = () => new WebsiteMenu()

+ 143 - 0
composables/layout/useMenu.ts

@@ -0,0 +1,143 @@
+import { ItemMenu, ItemsMenu } from '~/types/interfaces'
+import { getAccessMenu } from '~/composables/layout/Menus/accessMenu'
+import { getAgendaMenu } from '~/composables/layout/Menus/agendaMenu'
+import { getEquipmentMenu } from '~/composables/layout/Menus/equipmentMenu'
+import { getEducationalMenu } from '~/composables/layout/Menus/educationalMenu'
+import { getBillingMenu } from '~/composables/layout/Menus/billingMenu'
+import { getCommunicationMenu } from '~/composables/layout/Menus/communicationMenu'
+import { getDonorsMenu } from '~/composables/layout/Menus/donorsMenu'
+import { getMedalsMenu } from '~/composables/layout/Menus/medalsMenu'
+import { getStatsMenu } from '~/composables/layout/Menus/statsMenu'
+import { getCotisationsMenu } from '~/composables/layout/Menus/cotisationsMenu'
+import { getAdmin2iosMenu } from '~/composables/layout/Menus/admin2iosMenu'
+import { getWebsiteMenu } from '~/composables/layout/Menus/websiteMenu'
+import { getConfigurationMenu } from '~/composables/layout/Menus/configurationMenu'
+import { getMyFamilyMenu } from '~/composables/layout/Menus/myFamilyMenu'
+import { getMyAccessesMenu } from '~/composables/layout/Menus/myAccessesMenu'
+import { getAccountMenu } from '~/composables/layout/Menus/accountMenu'
+import {getParametersMenu} from "~/composables/layout/Menus/parametersMenu";
+import {Ref} from "@vue/reactivity";
+import {useProfileAccessStore} from "~/store/profile/access";
+
+/**
+ * @category composables/layout
+ * @class Menu
+ * Use Classe pour la construction du Menu
+ */
+class Menu {
+
+  /**
+   * Construit le menu et mets à jour le state du profile d'access
+   */
+  getLateralMenu (): Ref {
+    const menu: ItemsMenu = []
+
+    const accessMenu: ItemMenu | null = getAccessMenu()
+    if (accessMenu) { menu.push(accessMenu) }
+
+    const agendaMenu: ItemMenu | null = getAgendaMenu()
+    if (agendaMenu) { menu.push(agendaMenu) }
+
+    const equipmentMenu: ItemMenu | null = getEquipmentMenu()
+    if (equipmentMenu) { menu.push(equipmentMenu) }
+
+    const educationalMenu: ItemMenu | null = getEducationalMenu()
+    if (educationalMenu) { menu.push(educationalMenu) }
+
+    const billingMenu: ItemMenu | null = getBillingMenu()
+    if (billingMenu) { menu.push(billingMenu) }
+
+    const communicationMenu: ItemMenu | null = getCommunicationMenu()
+    if (communicationMenu) { menu.push(communicationMenu) }
+
+    const donorsMenu: ItemMenu | null = getDonorsMenu()
+    if (donorsMenu) { menu.push(donorsMenu) }
+
+    const medalsMenu: ItemMenu | null = getMedalsMenu()
+    if (medalsMenu) { menu.push(medalsMenu) }
+
+    const websiteMenu: ItemMenu | null = getWebsiteMenu().getMenu()
+    if (websiteMenu) { menu.push(websiteMenu) }
+
+    const cotisationsMenu: ItemMenu | null = getCotisationsMenu()
+    if (cotisationsMenu) { menu.push(cotisationsMenu) }
+
+    const statsMenu: ItemMenu | null = getStatsMenu()
+    if (statsMenu) { menu.push(statsMenu) }
+
+    const admin2iosMenu: ItemMenu | null = getAdmin2iosMenu()
+    if (admin2iosMenu) { menu.push(admin2iosMenu) }
+
+    // Si l'utilisateur possède au moins un menu alors le menu latéral sera accessible
+    const profileAccessStore = useProfileAccessStore()
+    profileAccessStore.hasLateralMenu = menu.length > 0
+
+    return ref(menu)
+  }
+
+  /**
+   * Construit le menu configuration et met à jour le state du profile d'access
+   */
+  getConfigurationMenu (): Ref {
+    const menu: ItemMenu | null = getConfigurationMenu()
+
+    // Si l'utilisateur possède au moins un menu alors le menu configuration sera accessible
+    const profileAccessStore = useProfileAccessStore()
+    profileAccessStore.hasConfigurationMenu = menu != null
+
+    return ref(menu)
+  }
+
+  /**
+   * Construit le menu Mon Compte
+   */
+  getAccountMenu (): Ref {
+    return ref(getAccountMenu())
+  }
+
+  /**
+   * Construit le menu Mes structure et mets à jour le state du profile d'access
+   */
+  getMyAccessesMenu (): Ref {
+    const menu: ItemMenu | null = getMyAccessesMenu()
+    // Si l'utilisateur possède au moins un menu alors le menu mes structures sera accessible
+    const profileAccessStore = useProfileAccessStore()
+    profileAccessStore.hasAccessesMenu = menu != null
+    return ref(menu)
+  }
+
+  /**
+   * Construit le menu Changement d'utilisateur et mets à jour le state du profile d'access
+   */
+  getMyFamilyMenu (): Ref {
+    const menu: ItemMenu | null = getMyFamilyMenu()
+    // Si l'utilisateur possède au moins un menu alors le menu changement d'utilisateur sera accessible
+    const profileAccessStore = useProfileAccessStore()
+    profileAccessStore.hasFamilyMenu = menu != null
+    return ref(menu)
+  }
+
+  /**
+   * Construit le menu site internet du header
+   */
+  getWebSiteMenu (): Ref {
+    return ref(getWebsiteMenu().getHeaderMenu())
+  }
+
+  /**
+   * Construit le menu Paramètres
+   */
+  getParametersMenu (): Ref {
+    const menu: ItemsMenu | null = getParametersMenu()
+
+    // Si l'utilisateur possède au moins un menu alors le menu latéral sera accessible
+    const profileAccessStore = useProfileAccessStore()
+    profileAccessStore.hasLateralMenu = true
+
+    return ref(menu)
+  }
+}
+
+export function UseMenu() {
+  return new Menu()
+}

File diff suppressed because it is too large
+ 59 - 0
layouts/default.vue


+ 40 - 0
nuxt.config.ts

@@ -3,6 +3,40 @@ import fs from 'fs';
 
 // https://v3.nuxtjs.org/api/configuration/nuxt.config
 export default defineNuxtConfig({
+    vuetify: {
+        customVariables: ['~/assets/css/variables.scss'],
+        treeShake: true,
+        theme: {
+            options: {
+                customProperties: true
+            },
+            dark: false,
+            themes: {
+                light: {
+                    ot_green: '#00ad8e',
+                    ot_light_green: '#a9e0d6',
+                    ot_dark_grey: '#324150',
+                    ot_dark_grey_hover: '#2c3a48',
+                    ot_grey: '#777777',
+                    ot_header_menu: '#ECE7E5',
+                    ot_light_grey: '#f5f5f5',
+                    ot_super_light_grey: '#ecf0f5',
+                    ot_danger: '#f56954',
+                    ot_success: '#00a65a',
+                    ot_warning: '#f39c12',
+                    ot_info: '#3c8dbc',
+                    ot_menu_color: '#b8c7ce',
+                    ot_content_color: '#ecf0f4',
+                    ot_border_menu: '#f4f4f4',
+                    ot_white: '#ffffff',
+                    ot_black: '#000000'
+                },
+            }
+        }
+    },
+    css: [
+        'vuetify/lib/styles/main.sass'
+    ],
     runtimeConfig: {
         // Private config that is only available on the server
         baseUrl: '',
@@ -63,7 +97,13 @@ export default defineNuxtConfig({
     typescript: {
         strict: true
     },
+    build: {
+        transpile: ['vuetify'],
+    },
     vite: {
+        define: {
+            'process.env.DEBUG': false,
+        },
         server: {
             https: {
                 key: fs.readFileSync('local.app-v3.opentalent.fr.key'),

+ 2 - 0
package.json

@@ -22,6 +22,8 @@
     "@types/js-yaml": "^4.0.5",
     "js-yaml": "^4.1.0",
     "pinia-orm": "^1.0.0-rc.5",
+    "sass": "^1.54.5",
+    "vuetify": "^3.0.0-beta.9",
     "yaml-import": "^2.0.0"
   }
 }

+ 3 - 15
pages/index.vue

@@ -1,22 +1,10 @@
 <template>
-  <h1>Index page r s</h1>
-    <div v-if="$can('create', 'tagg')">
-      <a @click="createPost">Add Post</a>
-    </div>
-
-<!--  {{store}}-->
+  <main>
+    Test
+  </main>
 </template>
 
 <script setup lang="ts">
-import {useProfileAccessStore} from "~/store/profile/access";
-import {repositoryHelper} from "~/services/store/repository";
-import {MyProfile} from "~/models/Access/MyProfile";
-import {useDataDeleter} from "~/composables/data/useDataDeleter";
-// const store = useProfileAccessStore()
-
-
-//const myProfile = repositoryHelper.findItemFromModel(MyProfile, 20840)
-
 
 </script>
 

+ 11 - 0
plugins/vuetify.ts

@@ -0,0 +1,11 @@
+import { createVuetify } from 'vuetify'
+import * as components from 'vuetify/components'
+import * as directives from 'vuetify/directives'
+
+export default defineNuxtPlugin(nuxtApp => {
+    const vuetify = createVuetify({
+        components,
+        directives,
+    })
+    nuxtApp.vueApp.use(vuetify)
+})

+ 3 - 5
services/profile/organizationProfile.ts

@@ -1,5 +1,4 @@
 import { AnyJson, organizationState } from '~/types/interfaces'
-import {Pinia} from "pinia";
 import {useProfileOrganizationStore} from "~/store/profile/organization";
 /**
  * L'OrganizationProfile permet de manipuler l'OrganizationState auquel
@@ -12,10 +11,9 @@ class OrganizationProfile {
 
   /**
    * @constructor
-   * @param {Pinia} pinia State organization du store contenant les informations de l'organisation
    */
-  constructor (pinia: Pinia) {
-    this.organizationProfile = useProfileOrganizationStore(pinia)
+  constructor () {
+    this.organizationProfile = useProfileOrganizationStore()
   }
 
   /**
@@ -180,4 +178,4 @@ class OrganizationProfile {
   }
 }
 
-export const $organizationProfile = (pinia:Pinia) => new OrganizationProfile(pinia)
+export const $organizationProfile = () => new OrganizationProfile()

+ 1 - 1
types/interfaces.d.ts

@@ -27,7 +27,7 @@ declare module '@vuex-orm/core' {
 
 interface IconItem{
   name?: string,
-  avatarId?:number,
+  avatarId?:number|null,
   avatarByDefault?:string,
 }
 

+ 21 - 2
yarn.lock

@@ -1346,7 +1346,7 @@ chardet@^0.7.0:
   resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
   integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
 
-chokidar@^3.5.1, chokidar@^3.5.3:
+"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1, chokidar@^3.5.3:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
   integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
@@ -3068,6 +3068,11 @@ ignore@^5.1.1, ignore@^5.2.0:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
   integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
 
+immutable@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
+  integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
+
 import-fresh@^3.0.0, import-fresh@^3.2.1:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -4881,6 +4886,15 @@ safe-regex@^2.1.1:
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
+sass@^1.54.5:
+  version "1.54.5"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.5.tgz#93708f5560784f6ff2eab8542ade021a4a947b3a"
+  integrity sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw==
+  dependencies:
+    chokidar ">=3.0.0 <4.0.0"
+    immutable "^4.0.0"
+    source-map-js ">=0.6.2 <2.0.0"
+
 schema-utils@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
@@ -5013,7 +5027,7 @@ slash@^4.0.0:
   resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
   integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
 
-source-map-js@^1.0.2:
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
   integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@@ -5709,6 +5723,11 @@ vue@^3.2.37:
     "@vue/server-renderer" "3.2.37"
     "@vue/shared" "3.2.37"
 
+vuetify@^3.0.0-beta.9:
+  version "3.0.0-beta.9"
+  resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.0.0-beta.9.tgz#b04e4fc9b81d45d25933808e713d9df60348ee6c"
+  integrity sha512-UL30GE4HdzllkqfcEVThL2UF8AVsALRP7b7n7JqLHp4Pm24QZqiP534cp7A4aGDiX7HXhpjZA/ava0PSarQ+uQ==
+
 wcwidth@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"

Some files were not shown because too many files changed in this diff