Просмотр исходного кода

Merge branch 'feature/reread-and-comment' into develop

Olivier Massot 4 лет назад
Родитель
Сommit
77c17d624a
100 измененных файлов с 3038 добавлено и 2039 удалено
  1. 1 0
      .gitignore
  2. 15 0
      .gitlab-ci.yml
  3. 36 1
      README.md
  4. BIN
      assets/vue_js_logo.png
  5. 17 14
      components/Layout/Alert/Container.vue
  6. 43 42
      components/Layout/Alert/Content.vue
  7. 5 3
      components/Layout/BannerTop.vue
  8. 5 2
      components/Layout/Container.vue
  9. 14 12
      components/Layout/Dialog.vue
  10. 70 63
      components/Layout/Header.vue
  11. 37 29
      components/Layout/Header/Menu.vue
  12. 27 22
      components/Layout/Header/Notification.vue
  13. 23 21
      components/Layout/Loading.vue
  14. 30 21
      components/Layout/Menu.vue
  15. 32 29
      components/Layout/SubHeader/ActivityYear.vue
  16. 36 36
      components/Layout/SubHeader/Breadcrumbs.vue
  17. 43 48
      components/Layout/SubHeader/DataTiming.vue
  18. 46 44
      components/Layout/SubHeader/DataTimingRange.vue
  19. 62 62
      components/Layout/SubHeader/PersonnalizedList.vue
  20. 18 14
      components/Layout/Subheader.vue
  21. 43 40
      components/Ui/Button/Delete.vue
  22. 39 34
      components/Ui/Card.vue
  23. 97 93
      components/Ui/DataTable.vue
  24. 32 23
      components/Ui/ExpansionPanel.vue
  25. 113 107
      components/Ui/Form.vue
  26. 71 64
      components/Ui/Input/Autocomplete.vue
  27. 83 75
      components/Ui/Input/AutocompleteWithAPI.vue
  28. 34 26
      components/Ui/Input/Checkbox.vue
  29. 75 68
      components/Ui/Input/DatePicker.vue
  30. 57 47
      components/Ui/Input/Enum.vue
  31. 63 52
      components/Ui/Input/Text.vue
  32. 72 66
      components/Ui/Map.vue
  33. 53 50
      components/Ui/SubList.vue
  34. 54 46
      components/Ui/Xeditable/Text.vue
  35. 3 9
      config/nuxtConfig/build.js
  36. 2 2
      config/nuxtConfig/env.js
  37. 7 6
      config/nuxtConfig/head.js
  38. 4 2
      config/nuxtConfig/modules.js
  39. 1 1
      config/nuxtConfig/plugins.js
  40. 1 1
      config/nuxtConfig/vuetify.js
  41. 1 1
      jest.config.js
  42. 56 56
      lang/enum/fr-FR.js
  43. 57 57
      lang/field/fr-FR.js
  44. 2 2
      lang/form/fr-FR.js
  45. 1 2
      lang/fr-FR.js
  46. 12 6
      lang/layout/fr-FR.js
  47. 2 2
      lang/menuKey/fr-FR.js
  48. 2 2
      lang/rulesAndErrors/fr-FR.js
  49. 37 36
      layouts/default.vue
  50. 19 19
      layouts/error.vue
  51. 5 5
      layouts/login.vue
  52. 1 1
      middleware/auth.ts
  53. 4 4
      models/Access/MyProfile.ts
  54. 2 2
      models/Access/PersonalizedList.ts
  55. 11 11
      models/Core/AddressPostal.ts
  56. 8 8
      models/Core/BankAccount.ts
  57. 11 11
      models/Core/ContactPoint.ts
  58. 2 2
      models/Core/Country.ts
  59. 37 37
      models/Organization/Organization.ts
  60. 4 4
      models/Organization/OrganizationAddressPostal.ts
  61. 6 6
      models/Organization/OrganizationLicence.ts
  62. 6 6
      models/Organization/OrganizationNetwork.ts
  63. 1 2
      nuxt.config.js
  64. 48 46
      package.json
  65. 12 9
      pages/index.vue
  66. 11 10
      pages/login.vue
  67. 42 30
      pages/organization.vue
  68. 77 76
      pages/organization/address/_id.vue
  69. 84 80
      pages/organization/index.vue
  70. 321 0
      pages/organization/subscription.vue
  71. 3 2
      plugins/Data/axios.js
  72. 4 4
      plugins/Data/dataDeleter.ts
  73. 4 4
      plugins/Data/dataPersister.ts
  74. 4 4
      plugins/Data/dataProvider.ts
  75. 4 4
      plugins/Rights/ability.ts
  76. 4 4
      plugins/Rights/casl.js
  77. 2 2
      plugins/helpersInit.ts
  78. 6 6
      plugins/route.ts
  79. 46 44
      services/connection/connection.ts
  80. 0 84
      services/connection/constructUrl.ts
  81. 100 0
      services/connection/urlBuilder.ts
  82. 6 4
      services/data/__mocks__/__dataProvider.ts
  83. 76 0
      services/data/baseDataManager.ts
  84. 33 0
      services/data/dataDeleter.ts
  85. 66 0
      services/data/dataPersister.ts
  86. 69 0
      services/data/dataProvider.ts
  87. 35 0
      services/data/hookable.ts
  88. 16 0
      services/data/hooks/baseHook.ts
  89. 5 0
      services/data/hooks/hookDeleter/_import.ts
  90. 18 0
      services/data/hooks/hookDeleter/hookDeleterExample.ts
  91. 5 0
      services/data/hooks/hookPersister/_import.ts
  92. 17 0
      services/data/hooks/hookPersister/hookPersisterExample.ts
  93. 5 0
      services/data/hooks/hookProvider/_import.ts
  94. 17 0
      services/data/hooks/hookProvider/hookProviderExample.ts
  95. 9 0
      services/data/processor/_import.ts
  96. 34 0
      services/data/processor/baseProcessor.ts
  97. 24 0
      services/data/processor/defaultProcessor.ts
  98. 48 0
      services/data/processor/enumProcessor.ts
  99. 32 0
      services/data/processor/modelProcessor.ts
  100. 0 69
      services/dataDeleter/dataDeleter.ts

+ 1 - 0
.gitignore

@@ -88,3 +88,4 @@ sw.*
 
 # Vim swap files
 *.swp
+/.project

+ 15 - 0
.gitlab-ci.yml

@@ -0,0 +1,15 @@
+stages:
+  - test
+
+before_script:
+  - apt-get update
+  - curl -fsSL https://deb.nodesource.com/setup_14.x | bash -
+  - apt-get install -y nodejs
+  - npm install --global yarn
+  - yarn install
+
+unit:
+  stage: test
+
+  script:
+    - yarn test

+ 36 - 1
README.md

@@ -1,6 +1,20 @@
 # admin
 
-## Build Setup
+[![pipeline status](http://gitlab.2iopenservice.com/vincent/admin/badges/master/pipeline.svg)](http://gitlab.2iopenservice.com/vincent/admin/-/commits/master)
+[![coverage report](http://gitlab.2iopenservice.com/vincent/admin/badges/master/coverage.svg)](http://gitlab.2iopenservice.com/vincent/admin/-/commits/master)
+
+Frontend Opentalent développé avec Vue.js 2 + NuxtJs 2
+
+A voir:
+
+* [vuejs.org](https://vuejs.org/v2/guide/)
+* [nuxtjs.org](https://fr.nuxtjs.org/docs/2.x/get-started/installation)
+* [vuex-orm.org](https://vuex-orm.org/)
+* [vuetifyjs.com](https://vuetifyjs.com/en/)
+* [typescriptlang.org](https://www.typescriptlang.org/)
+* [jestjs.io](https://jestjs.io/docs/getting-started)
+
+### Build Setup
 
 ```bash
 # install dependencies
@@ -21,3 +35,24 @@ $ yarn docs
 ```
 
 For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
+
+## Structure du projet
+
+| Répertoire | Rôle |
+| --- | --- |
+| `assets` | Contient les fichiers style et medias |
+| `components` | Les différents composants graphiques qui composent l'application |
+| `config` | La configuration de l'application |
+| `lang` | Les fichiers de traduction |
+| `layouts` | Layouts des pages |
+| `middleware` | Code exécuté avant le rendu des pages (ex: pour vérifier l'authentification) |
+| `models` | Définition des entités (ou modèles) |
+| `node_modules` | Modules node installés via npm |
+| `pages` | Définition des pages qui composent l'application |
+| `plugins` | ... |
+| `services` | Rassemble les classes utilitaires non graphiques |
+| `static` | Ressources statiques et publiques |
+| `store` | Le store et ses composants servent d'entrepôt de donnés, et s'assurent de la cohérence de celles-ci |
+| `tests` | Regroupe les tests (unitaires, end-to-end...) |
+| `use` | ... |
+

BIN
assets/vue_js_logo.png


+ 17 - 14
components/Layout/Alert/Container.vue

@@ -1,31 +1,34 @@
+<!--
+Container principal pour l'affichage d'une ou plusieurs alertes
+-->
+
 <template>
   <main class="alertContainer">
     <LayoutAlertContent
-      class="alertContent"
       v-for="(alert, key) in alerts"
       :key="key"
+      class="alertContent"
       :alert="alert"
     />
   </main>
 </template>
 
 <script lang="ts">
-  import {defineComponent, computed, ComputedRef} from "@vue/composition-api";
-  import {useContext} from "@nuxtjs/composition-api";
-  import {alert} from "~/types/interfaces"
+import { defineComponent, computed, ComputedRef, useContext } from '@nuxtjs/composition-api'
+import { alert } from '~/types/interfaces'
 
-  export default defineComponent({
-    setup() {
-      const {store} = useContext()
+export default defineComponent({
+  setup () {
+    const { store } = useContext()
 
-      const alerts:ComputedRef<Array<alert>> = computed(() => {
-        return store.state.page.alerts
-      })
-      return {
-        alerts
-      }
+    const alerts: ComputedRef<Array<alert>> = computed(() => {
+      return store.state.page.alerts
+    })
+    return {
+      alerts
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 43 - 42
components/Layout/Alert/Content.vue

@@ -1,3 +1,5 @@
+<!-- Message d'alerte -->
+
 <template>
   <v-alert
     v-model="show"
@@ -7,54 +9,53 @@
     width="400"
     dismissible
     transition="fade-transition"
-    v-on:mouseover="onMouseOver"
-    v-on:mouseout="onMouseOut"
+    @mouseover="onMouseOver"
+    @mouseout="onMouseOut"
   >
-    {{$t(alert.message)}}
+    {{ $t(alert.message) }}
   </v-alert>
 </template>
 
 <script lang="ts">
-  import {defineComponent, ref, Ref} from "@vue/composition-api";
-  import {alert} from "~/types/interfaces";
-  import {useContext} from "@nuxtjs/composition-api";
-
-  export default defineComponent({
-    props: {
-      alert: {
-        type: Object as () => alert,
-        required: true
-      }
-    },
-    setup() {
-      const {store} = useContext()
-      const show:Ref<boolean> = ref(true)
-      let timeout: any = null;
-
-      const clearAlert = (time:number = 2000) => {
-        timeout = setTimeout(() => {
-          show.value = false;
-          store.dispatch('page/removeSlowlyAlert')
-        }, time)
-      }
-
-      const onMouseOver = () => {
-        clearTimeout(timeout);
-      }
-
-      const onMouseOut = () => {
-        clearAlert(1000);
-      }
-
-      clearAlert()
-
-      return {
-        show,
-        onMouseOver,
-        onMouseOut
-      }
+import { defineComponent, ref, Ref, useContext } from '@nuxtjs/composition-api'
+import { alert } from '~/types/interfaces'
+
+export default defineComponent({
+  props: {
+    alert: {
+      type: Object as () => alert,
+      required: true
+    }
+  },
+  setup () {
+    const { store } = useContext()
+    const show: Ref<boolean> = ref(true)
+    let timeout: any = null
+
+    const clearAlert = (time: number = 2000) => {
+      timeout = setTimeout(() => {
+        show.value = false
+        store.dispatch('page/removeSlowlyAlert')
+      }, time)
+    }
+
+    const onMouseOver = () => {
+      clearTimeout(timeout)
+    }
+
+    const onMouseOut = () => {
+      clearAlert(1000)
+    }
+
+    clearAlert()
+
+    return {
+      show,
+      onMouseOver,
+      onMouseOut
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 5 - 3
components/Layout/BannerTop.vue

@@ -1,13 +1,15 @@
+<!-- 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="bloc1"></slot>
+      <slot name="block1" />
     </v-col>
     <v-col cols="6" class="ot_white ot_grey--text">
-      <slot name="bloc2"></slot>
+      <slot name="block2" />
     </v-col>
     <v-col cols="3" class="ot_light_grey ot_grey--text">
-      <slot name="bloc3"></slot>
+      <slot name="block3" />
     </v-col>
   </v-row>
 </template>

+ 5 - 2
components/Layout/Container.vue

@@ -1,8 +1,11 @@
+<!-- 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></slot>
+        <slot />
       </v-col>
     </v-row>
   </v-container>
@@ -10,6 +13,6 @@
 
 <style scoped>
   .container{
-    padding-top: 0px;
+    padding-top: 0;
   }
 </style>

+ 14 - 12
components/Layout/Dialog.vue

@@ -1,3 +1,5 @@
+<!-- Fenêtre de dialogue -->
+
 <template>
   <v-dialog
     v-model="show"
@@ -5,29 +7,29 @@
     max-width="800"
   >
     <v-card>
-      <slot name="dialogText"></slot>
+      <slot name="dialogText" />
 
-      <v-divider></v-divider>
+      <v-divider />
 
       <v-card-actions>
-        <v-spacer></v-spacer>
-          <slot name="dialogBtn"></slot>
+        <v-spacer />
+        <slot name="dialogBtn" />
       </v-card-actions>
     </v-card>
   </v-dialog>
 </template>
 
 <script lang="ts">
-  import {defineComponent} from "@vue/composition-api";
+import { defineComponent } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    props: {
-      show : {
-        type: Boolean,
-        required: true
-      }
+export default defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      required: true
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 70 - 63
components/Layout/Header.vue

@@ -1,3 +1,8 @@
+<!--
+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
@@ -13,20 +18,24 @@
       icon
       @click.stop="displayedMenu()"
     >
-      <v-icon class="ot_white--text">mdi-menu{{ `${properties.miniVariant ? '' : '-open'}` }}</v-icon>
+      <v-icon class="ot_white--text">
+        mdi-menu{{ `${properties.miniVariant ? '' : '-open'}` }}
+      </v-icon>
     </v-btn>
 
-    <v-toolbar-title v-text="title"/>
+    <v-toolbar-title v-text="title" />
 
-    <v-spacer/>
+    <v-spacer />
 
     <v-btn
       elevation="2"
       color="ot_warning ot_white--text"
-    >{{$t('create')}}</v-btn>
+    >
+      {{ $t('create') }}
+    </v-btn>
 
     <v-tooltip bottom>
-      <template v-slot:activator="{ on, attrs }">
+      <template #activator="{ on, attrs }">
         <v-btn
           icon
           v-bind="attrs"
@@ -35,84 +44,82 @@
           <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>
+      <span>{{ $t('welcome') }}</span>
     </v-tooltip>
 
+    <LayoutHeaderMenu :menu="webSiteMenu" />
 
-    <LayoutHeaderMenu :menu="webSiteMenu"></LayoutHeaderMenu>
-
-    <LayoutHeaderMenu :menu="myAccessesMenu" v-if="hasAccessesMenu"></LayoutHeaderMenu>
+    <LayoutHeaderMenu v-if="hasAccessesMenu" :menu="myAccessesMenu" />
 
-    <LayoutHeaderMenu :menu="myFamilyMenu" v-if="hasFamilyMenu"></LayoutHeaderMenu>
+    <LayoutHeaderMenu v-if="hasFamilyMenu" :menu="myFamilyMenu" />
 
-    <LayoutHeaderNotification></LayoutHeaderNotification>
+    <LayoutHeaderNotification />
 
-    <LayoutHeaderMenu :menu="configurationMenu" v-if="hasConfigurationMenu"></LayoutHeaderMenu>
+    <LayoutHeaderMenu v-if="hasConfigurationMenu" :menu="configurationMenu" />
 
-    <LayoutHeaderMenu :menu="accountMenu" :avatar="true"></LayoutHeaderMenu>
+    <LayoutHeaderMenu :menu="accountMenu" :avatar="true" />
 
     <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>
+      <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} from '@nuxtjs/composition-api'
-  import {$useMenu} from "~/use/layout/menu";
-  import {UnwrapRef} from "@vue/composition-api";
-  import {AnyJson} from "~/types/interfaces";
-
-  export default defineComponent({
-    setup(props, {emit}) {
-      const {store, $config} = useContext();
-
-      const properties:UnwrapRef<AnyJson> = reactive({
-        miniVariant: 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 displayedMenu = () => {
-        properties.miniVariant = !properties.miniVariant
-        emit('handle-open-menu-click', properties.miniVariant)
-      }
-
-      return {
-        properties,
-        displayedMiniVariant,
-        hasConfigurationMenu,
-        hasAccessesMenu,
-        hasFamilyMenu,
-        title,
-        displayedMenu,
-        webSiteMenu,
-        myAccessesMenu,
-        myFamilyMenu,
-        configurationMenu,
-        accountMenu
-      }
+import {
+  defineComponent, reactive, useContext, computed, ComputedRef, Ref, UnwrapRef
+} from '@nuxtjs/composition-api'
+import { $useMenu } from '~/use/layout/menu'
+import { AnyJson } from '~/types/interfaces'
+
+export default defineComponent({
+  setup (_props, { emit }) {
+    const { store, $config } = useContext()
+
+    const properties:UnwrapRef<AnyJson> = reactive({
+      miniVariant: 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 displayedMenu = () => {
+      properties.miniVariant = !properties.miniVariant
+      emit('handle-open-menu-click', properties.miniVariant)
+    }
+
+    return {
+      properties,
+      displayedMiniVariant,
+      hasConfigurationMenu,
+      hasAccessesMenu,
+      hasFamilyMenu,
+      title,
+      displayedMenu,
+      webSiteMenu,
+      myAccessesMenu,
+      myFamilyMenu,
+      configurationMenu,
+      accountMenu
     }
-  })
+  }
+})
 </script>
 
 <style scoped>
   .help {
-    padding: 14px;
-    padding-bottom: 13px;
+    padding: 14px 14px 13px;
     font-size: 14px;
     text-decoration: none;
   }

+ 37 - 29
components/Layout/Header/Menu.vue

@@ -1,13 +1,19 @@
+<!--
+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 max-height="300">
     <template v-slot:activator="{ on: { click }, attrs }">
       <v-tooltip bottom>
         <template v-slot:activator="{ on: on_tooltips , attrs: attrs_tooltips }">
-          <v-avatar v-if="avatar"
-                 size="30"
-                 v-bind="[attrs, attrs_tooltips]"
-                 v-on="on_tooltips"
-                 v-on:click="click"
+          <v-avatar
+            v-if="avatar"
+            size="30"
+            v-bind="[attrs, attrs_tooltips]"
+            v-on="on_tooltips"
+            @click="click"
           >
             <img
               src="https://cdn.vuetifyjs.com/images/john.jpg"
@@ -15,22 +21,25 @@
             >
           </v-avatar>
 
-          <v-btn v-else
-                 icon
-                 v-bind="[attrs, attrs_tooltips]"
-                 v-on="on_tooltips"
-                 v-on:click="click"
-                 color=""
+          <v-btn
+            v-else
+            icon
+            v-bind="[attrs, attrs_tooltips]"
+            color=""
+            v-on="on_tooltips"
+            @click="click"
           >
-            <v-icon class="ot_white--text" small>{{menu.icon}}</v-icon>
+            <v-icon class="ot_white--text" small>
+              {{ menu.icon }}
+            </v-icon>
           </v-btn>
         </template>
-        <span>{{$t(menu.title)}}</span>
+        <span>{{ $t(menu.title) }}</span>
       </v-tooltip>
     </template>
     <v-list dense :subheader="true">
       <v-list-item dense class="ot_light_grey">
-        <v-list-item-title v-text="$t(menu.title)"></v-list-item-title>
+        <v-list-item-title v-text="$t(menu.title)" />
       </v-list-item>
       <template v-for="(item, index) in menu.children">
         <v-list-item
@@ -40,31 +49,30 @@
           router
           exact
         >
-          <v-list-item-title v-text="$t(item.title)"></v-list-item-title>
+          <v-list-item-title v-text="$t(item.title)" />
         </v-list-item>
         <v-divider
           v-if="index < menu.length - 1"
           :key="index"
-        ></v-divider>
+        />
       </template>
-
     </v-list>
   </v-menu>
 </template>
 
 <script lang="ts">
-  import {defineComponent} from '@nuxtjs/composition-api'
+import { defineComponent } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    props: {
-      menu: {
-        type: Object,
-        required: true
-      },
-      avatar: {
-        type: Boolean,
-        required: false
-      }
+export default defineComponent({
+  props: {
+    menu: {
+      type: Object,
+      required: true
+    },
+    avatar: {
+      type: Boolean,
+      required: false
     }
-  })
+  }
+})
 </script>

+ 27 - 22
components/Layout/Header/Notification.vue

@@ -1,52 +1,57 @@
+<!--
+Notification
+-->
+
 <template>
   <v-menu offset-y>
     <template v-slot:activator="{ on, attrs }">
       <v-tooltip bottom>
         <template v-slot:activator="{ on, attrs }">
-          <v-btn icon
-                 v-bind="attrs"
-                 v-on="on"
+          <v-btn
+            icon
+            v-bind="attrs"
+            v-on="on"
           >
-            <v-icon class="ot_white--text" small>fa-bell</v-icon>
+            <v-icon class="ot_white--text" small>
+              fa-bell
+            </v-icon>
           </v-btn>
         </template>
-        <span>{{$t('notification')}}</span>
+        <span>{{ $t('notification') }}</span>
       </v-tooltip>
     </template>
     <v-list dense>
       <template v-for="(item, index) in properties.menu">
         <v-list-item :key="item.title">
-          <v-list-item-title v-text="$t(item.title)"></v-list-item-title>
+          <v-list-item-title v-text="$t(item.title)" />
         </v-list-item>
         <v-divider
           v-if="index < properties.menu.length - 1"
           :key="index"
-        ></v-divider>
+        />
       </template>
-
     </v-list>
   </v-menu>
 </template>
 
 <script lang="ts">
-  import {$useMenu} from '@/use/layout/menu'
-  import {defineComponent, reactive, Ref} from '@nuxtjs/composition-api'
-  import {AnyJson} from "~/types/interfaces";
-  import {UnwrapRef} from "@vue/composition-api";
+import { defineComponent, reactive, Ref, UnwrapRef } from '@nuxtjs/composition-api'
+import { $useMenu } from '@/use/layout/menu'
+import { AnyJson } from '~/types/interfaces'
 
-  export default defineComponent({
-    setup() {
-      const menu:Ref<any> = $useMenu.setUpContext().useConfigurationMenuConstruct()
+export default defineComponent({
+  setup () {
+    const menu: Ref<any> = $useMenu.setupContext().useConfigurationMenuConstruct()
 
-      const properties:UnwrapRef<AnyJson> = reactive({
-        menu: menu
-      })
+    const properties: UnwrapRef<AnyJson> = reactive({
+      menu
+    })
 
-      return {
-        properties
-      }
+    return {
+      properties
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 23 - 21
components/Layout/Loading.vue

@@ -1,37 +1,39 @@
+<!-- Animation circulaire à afficher durant les chargements -->
+
 <template>
   <v-overlay :value="loading" class="loading-page">
     <v-progress-circular
       indeterminate
       size="64"
-    ></v-progress-circular>
+    />
   </v-overlay>
 </template>
 
 <script lang="ts">
-  import {defineComponent, ref, Ref} from '@nuxtjs/composition-api'
+import { defineComponent, ref, Ref } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    setup() {
-      const loading:Ref<boolean> = ref(false)
+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 set = (_num: number) => {
+      loading.value = true
+    }
+    const start = () => {
+      loading.value = true
+    }
+    const finish = () => {
+      loading.value = false
+    }
 
-      return {
-        loading,
-        start,
-        finish,
-        set
-      }
+    return {
+      loading,
+      start,
+      finish,
+      set
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 30 - 21
components/Layout/Menu.vue

@@ -1,3 +1,8 @@
+<!--
+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"
@@ -16,10 +21,12 @@
           exact
         >
           <v-list-item-action>
-            <v-icon class="ot_menu_color--text" small>{{ item.icon }}</v-icon>
+            <v-icon class="ot_menu_color--text" small>
+              {{ item.icon }}
+            </v-icon>
           </v-list-item-action>
           <v-list-item-content>
-            <v-list-item-title class="ot_menu_color--text" v-text="$t(item.title)"/>
+            <v-list-item-title class="ot_menu_color--text" v-text="$t(item.title)" />
           </v-list-item-content>
         </v-list-item>
 
@@ -30,10 +37,12 @@
         >
           <template v-slot:activator>
             <v-list-item-action>
-              <v-icon class="ot_menu_color--text" small>{{ item.icon }}</v-icon>
+              <v-icon class="ot_menu_color--text" small>
+                {{ item.icon }}
+              </v-icon>
             </v-list-item-action>
             <v-list-item-content>
-              <v-list-item-title class="ot_menu_color--text" v-text="$t(item.title)"/>
+              <v-list-item-title class="ot_menu_color--text" v-text="$t(item.title)" />
             </v-list-item-content>
           </template>
 
@@ -46,36 +55,36 @@
             exact
           >
             <v-list-item-action>
-              <v-icon class="ot_menu_color--text" small>{{ child.icon }}</v-icon>
+              <v-icon class="ot_menu_color--text" small>
+                {{ child.icon }}
+              </v-icon>
             </v-list-item-action>
             <v-list-item-content>
-              <v-list-item-title class="ot_menu_color--text" v-text="$t(child.title)"/>
+              <v-list-item-title class="ot_menu_color--text" v-text="$t(child.title)" />
             </v-list-item-content>
           </v-list-item>
         </v-list-group>
       </div>
-
     </v-list>
-
   </v-navigation-drawer>
 </template>
 
 <script lang="ts">
-  import {defineComponent} from '@nuxtjs/composition-api'
-  import {ItemsMenu} from "~/types/interfaces";
+import { defineComponent } from '@nuxtjs/composition-api'
+import { ItemsMenu } from '~/types/interfaces'
 
-  export default defineComponent({
-    props: {
-      menu: {
-        type: Array as () => ItemsMenu,
-        required: true
-      },
-      miniVariant: {
-        type: Boolean,
-        required: true
-      }
+export default defineComponent({
+  props: {
+    menu: {
+      type: Array as () => ItemsMenu,
+      required: true
+    },
+    miniVariant: {
+      type: Boolean,
+      required: true
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 32 - 29
components/Layout/SubHeader/ActivityYear.vue

@@ -1,51 +1,54 @@
 <template>
   <main class="d-flex">
-    <span class="mr-2 ot_dark_grey--text font-weight-bold">{{$t(label)}} : </span>
+    <span class="mr-2 ot_dark_grey--text font-weight-bold">{{ $t(label) }} : </span>
     <UiXeditableText
       class="activity-year-input"
       type="number"
       :data="activityYear"
-      v-on:update="updateActivityYear">
+      @update="updateActivityYear"
+    >
       <template v-slot: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>
+        <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 {$useMyProfileUpdater} from "~/use/updater/useMyProfileUpdater";
-  import {$organizationProfile} from "~/services/profile/organizationProfile";
-  import {$useDirtyForm} from "~/use/form/useDirtyForm";
+import { defineComponent, useContext } from '@nuxtjs/composition-api'
+import { $useMyProfileUpdater } from '~/use/updater/useMyProfileUpdater'
+import { $organizationProfile } from '~/services/profile/organizationProfile'
+import { $useDirtyForm } from '~/use/form/useDirtyForm'
 
-  export default defineComponent({
-    setup() {
-      const {store, $dataPersister} = useContext()
-      const {updateMyProfile, setActivityYear, activityYear} = $useMyProfileUpdater(store, $dataPersister)
-      const {markFormAsNotDirty} = $useDirtyForm(store)
+export default defineComponent({
+  setup () {
+    const { store, $dataPersister } = useContext()
+    const { updateMyProfile, setActivityYear, activityYear } = $useMyProfileUpdater(store, $dataPersister)
+    const { markFormAsNotDirty } = $useDirtyForm(store)
 
-      const organizationProfile = $organizationProfile(store);
+    const organizationProfile = $organizationProfile(store)
 
-      let yearPlusOne:boolean = !organizationProfile.isManagerProduct()
-      const label:string = organizationProfile.isSchool() ? 'schooling_year' : organizationProfile.isArtist() ? 'season_year' : 'cotisation_year'
+    const yearPlusOne:boolean = !organizationProfile.isManagerProduct()
+    const label:string = organizationProfile.isSchool() ? 'schooling_year' : organizationProfile.isArtist() ? 'season_year' : 'cotisation_year'
 
-      const updateActivityYear = async (newDate:number) =>{
-        markFormAsNotDirty()
-        setActivityYear(newDate)
-        await updateMyProfile()
-        window.location.reload()
-      }
+    const updateActivityYear = async (newDate:number) => {
+      markFormAsNotDirty()
+      setActivityYear(newDate)
+      await updateMyProfile()
+      window.location.reload()
+    }
 
-      return {
-        activityYear,
-        label,
-        yearPlusOne,
-        updateActivityYear
-      }
+    return {
+      activityYear,
+      label,
+      yearPlusOne,
+      updateActivityYear
     }
-  })
+  }
+})
 </script>
 
 <style lang="scss">

+ 36 - 36
components/Layout/SubHeader/Breadcrumbs.vue

@@ -1,48 +1,48 @@
 <template>
   <v-breadcrumbs
-    :items="items">
-  </v-breadcrumbs>
+    :items="items"
+  />
 </template>
 
 <script lang="ts">
-  import {defineComponent, computed, useContext, useRouter, ComputedRef} from '@nuxtjs/composition-api'
-  import {AnyJson} from "~/types/interfaces";
+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
+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
-            })
-          }
-        })
+    const items: ComputedRef<Array<AnyJson>> = computed(() => {
+      const crumbs:Array<AnyJson> = []
+      crumbs.push({
+        text: i18n.t('welcome'),
+        href: homeUrl
+      })
 
-        return crumbs
+      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 {
-        items
-      }
+      return crumbs
+    })
+
+    return {
+      items
     }
-  })
+  }
+})
 </script>

+ 43 - 48
components/Layout/SubHeader/DataTiming.vue

@@ -1,7 +1,6 @@
 <template>
   <main class="d-flex align-baseline">
-
-    <span class="mr-2 ot_dark_grey--text font-weight-bold">{{$t('display_data')}} : </span>
+    <span class="mr-2 ot_dark_grey--text font-weight-bold">{{ $t('display_data') }} : </span>
 
     <v-btn-toggle
       v-model="historicalBtn"
@@ -11,83 +10,79 @@
       multiple
     >
       <v-btn max-height="25" class="font-weight-normal text-caption">
-        {{$t('past')}}
+        {{ $t('past') }}
       </v-btn>
 
       <v-btn max-height="25" class="font-weight-normal text-caption">
-        {{$t('present')}}
+        {{ $t('present') }}
       </v-btn>
 
       <v-btn max-height="25" class="font-weight-normal text-caption">
-        {{$t('future')}}
+        {{ $t('future') }}
       </v-btn>
-
     </v-btn-toggle>
-
   </main>
 </template>
 
 <script lang="ts">
-  import {defineComponent, onUnmounted, ref, useContext, watch, Ref} from '@nuxtjs/composition-api'
-  import {$useDirtyForm} from "~/use/form/useDirtyForm";
-  import {$useMyProfileUpdater} from "~/use/updater/useMyProfileUpdater";
-  import {WatchStopHandle} from "@vue/composition-api";
+import { defineComponent, onUnmounted, ref, useContext, watch, Ref, WatchStopHandle } from '@nuxtjs/composition-api'
+import { $useDirtyForm } from '~/use/form/useDirtyForm'
+import { $useMyProfileUpdater } from '~/use/updater/useMyProfileUpdater'
 
-  export default defineComponent({
-    setup() {
-      const {store, $dataPersister} = useContext()
-      const {markFormAsNotDirty} = $useDirtyForm(store)
-      const {updateMyProfile, setHistorical, historical} = $useMyProfileUpdater(store, $dataPersister)
+export default defineComponent({
+  setup () {
+    const { store, $dataPersister } = useContext()
+    const { markFormAsNotDirty } = $useDirtyForm(store)
+    const { updateMyProfile, setHistorical, historical } = $useMyProfileUpdater(store, $dataPersister)
 
-      const historicalBtn:Ref<Array<number>> = initHistoricalBtn(historical.value)
+    const historicalBtn: Ref<Array<number>> = initHistoricalBtn(historical.value)
 
-      const unwatch:WatchStopHandle = watch(historicalBtn, async (newValue) => {
-        const historicalChoice:Array<string> = initHistoricalChoice(newValue)
+    const unwatch: WatchStopHandle = watch(historicalBtn, async (newValue) => {
+      const historicalChoice: Array<string> = initHistoricalChoice(newValue)
 
-        setHistorical(historicalChoice)
-        markFormAsNotDirty()
-        await updateMyProfile()
-        window.location.reload()
-      })
+      setHistorical(historicalChoice)
+      markFormAsNotDirty()
+      await updateMyProfile()
+      window.location.reload()
+    })
 
-      onUnmounted(()=>{
-        unwatch()
-      })
+    onUnmounted(() => {
+      unwatch()
+    })
 
-      return {
-        historicalBtn
-      }
+    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']
+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
+  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']
+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
+  const historicalChoice:Array<string> = []
+  for (const key in historical) {
+    historicalChoice.push(historicalArray[historical[key]])
   }
+  return historicalChoice
+}
 </script>
 
 <style scoped lang="scss">

+ 46 - 44
components/Layout/SubHeader/DataTimingRange.vue

@@ -1,16 +1,16 @@
 <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>
+      <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"
-        :singleLine="true"
-        v-on:update="updateDateTimeRange"/>
+        :single-line="true"
+        @update="updateDateTimeRange"
+      />
     </div>
 
     <v-tooltip bottom>
@@ -19,65 +19,67 @@
           class="time-btn"
           max-height="25"
           v-bind="attrs"
-          v-on="on"
-          @click="show=!show"
           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-icon color="ot_grey" class="font-weight-normal" x-small>
+            fas fa-history
+          </v-icon>
         </v-btn>
       </template>
-      <span>{{$t('history_help')}}</span>
+      <span>{{ $t('history_help') }}</span>
     </v-tooltip>
-
   </main>
 </template>
 
 <script lang="ts">
-  import {defineComponent, onUnmounted, ref, useContext, watch, computed, ComputedRef} from '@nuxtjs/composition-api'
-  import {$useMyProfileUpdater} from "~/use/updater/useMyProfileUpdater";
-  import {$useDirtyForm} from "~/use/form/useDirtyForm";
-  import {Ref, WatchStopHandle} from "@vue/composition-api";
+import {
+  defineComponent, onUnmounted, ref, useContext, watch, computed, ComputedRef, Ref, WatchStopHandle
+} from '@nuxtjs/composition-api'
+import { $useMyProfileUpdater } from '~/use/updater/useMyProfileUpdater'
+import { $useDirtyForm } from '~/use/form/useDirtyForm'
 
-  export default defineComponent({
-    setup(_, {emit}) {
-      const {store, $dataPersister} = useContext()
-      const {markFormAsNotDirty} = $useDirtyForm(store)
-      const {updateMyProfile, setHistoricalRange, historical} = $useMyProfileUpdater(store, $dataPersister)
+export default defineComponent({
+  setup (_, { emit }) {
+    const { store, $dataPersister } = useContext()
+    const { markFormAsNotDirty } = $useDirtyForm(store)
+    const { updateMyProfile, setHistoricalRange, historical } = $useMyProfileUpdater(store, $dataPersister)
 
-      const datesRange:ComputedRef<Array<string>> = computed(()=>{
-        return [historical.value.dateStart, historical.value.dateEnd]
-      })
+    const datesRange:ComputedRef<Array<string>> = 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 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)
-      })
+    const unwatch:WatchStopHandle = watch(show, (newValue) => {
+      emit('showDateTimeRange', newValue)
+    })
 
-      onUnmounted(()=>{
-        unwatch()
-      })
+    onUnmounted(() => {
+      unwatch()
+    })
 
-      const updateDateTimeRange = async (dates:Array<string>):Promise<any> =>{
-        setHistoricalRange(dates)
-        markFormAsNotDirty()
-        await updateMyProfile()
-        window.location.reload()
-      }
+    const updateDateTimeRange = async (dates:Array<string>): Promise<any> => {
+      setHistoricalRange(dates)
+      markFormAsNotDirty()
+      await updateMyProfile()
+      window.location.reload()
+    }
 
-      return {
-        show,
-        datesRange,
-        updateDateTimeRange
-      }
+    return {
+      show,
+      datesRange,
+      updateDateTimeRange
     }
-  })
+  }
+})
 </script>
 
 <style lang="scss">

+ 62 - 62
components/Layout/SubHeader/PersonnalizedList.vue

@@ -10,10 +10,10 @@
       <template v-slot:activator="{ on, attrs }">
         <span
           v-bind="attrs"
-          v-on="on"
           class="ot_green--text"
+          v-on="on"
         >
-          {{$t('my_list')}}
+          {{ $t('my_list') }}
         </span>
       </template>
       <v-list>
@@ -21,13 +21,12 @@
           <v-list-item-title>
             <UiInputAutocomplete
               :label="$t('searchList')"
-              :isLoading="isLoading"
+              :is-loading="isLoading"
               :items="items"
-              :itemText="['label']"
-              v-on:update="goOn"
-              :returnObject="true"
-            >
-            </UiInputAutocomplete>
+              :item-text="['label']"
+              :return-object="true"
+              @update="goOn"
+            />
           </v-list-item-title>
         </v-list-item>
       </v-list>
@@ -36,83 +35,84 @@
 </template>
 
 <script lang="ts">
-  import {computed, defineComponent, useContext, useFetch, ref, Ref, ComputedRef} from '@nuxtjs/composition-api'
-  import {QUERY_TYPE} from "~/types/enums";
-  import {PersonalizedList} from "~/models/Access/PersonalizedList";
-  import {repositoryHelper} from "~/services/store/repository";
-  import {Collection} from "@vuex-orm/core";
-  import {AnyJson} from "~/types/interfaces";
-  import {$objectProperties} from "~/services/utils/objectProperties";
+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 { $objectProperties } from '~/services/utils/objectProperties'
 
-  export default defineComponent({
-    fetchOnServer: false,
-    setup() {
-      const {$dataProvider, $config} = useContext()
-      const isLoading:Ref<boolean> = ref(true)
-      const homeUrl:string = $config.baseURL_adminLegacy
+export default defineComponent({
+  fetchOnServer: false,
+  setup () {
+    const { $dataProvider, $config } = useContext()
+    const isLoading: Ref<boolean> = ref(true)
+    const homeUrl:string = $config.baseURL_adminLegacy
 
-      useFetch(async ()=>{
-        await $dataProvider.invoke({
-          type: QUERY_TYPE.MODEL,
-          model: PersonalizedList
-        })
-        isLoading.value = false
+    useFetch(async () => {
+      await $dataProvider.invoke({
+        type: QUERY_TYPE.MODEL,
+        model: PersonalizedList
       })
+      isLoading.value = false
+    })
 
-      const items:ComputedRef<Array<AnyJson>>  = computed(() => {
-        const lists = repositoryHelper.findCollectionFromModel(PersonalizedList) as Collection<PersonalizedList>
+    const items:ComputedRef<Array<AnyJson>> = computed(() => {
+      const lists = repositoryHelper.findCollectionFromModel(PersonalizedList) as Collection<PersonalizedList>
 
-        let listsGroupByKeyMenu:Array<AnyJson> = groupListByKey(lists)
+      let listsGroupByKeyMenu:Array<AnyJson> = groupListByKey(lists)
 
-        listsGroupByKeyMenu = $objectProperties.sortObjectByKey(listsGroupByKeyMenu)
+      listsGroupByKeyMenu = $objectProperties.sortObjectByKey(listsGroupByKeyMenu)
 
-        return constructAutoCompleteItems(listsGroupByKeyMenu)
-      })
+      return constructAutoCompleteItems(listsGroupByKeyMenu)
+    })
 
-      const goOn = (list:PersonalizedList) => {
-        window.location.href = `${homeUrl}/${list.entity}/list/${list.id}`
-      }
+    const goOn = (list:PersonalizedList) => {
+      window.location.href = `${homeUrl}/${list.entity}/list/${list.id}`
+    }
 
-      return {
-        items,
-        isLoading,
-        goOn
-      }
+    return {
+      items,
+      isLoading,
+      goOn
     }
-  })
+  }
+})
 
-  /**
+/**
    * On regroupe la list par clé afin de constituer les groups
    * @param lists
    */
-  function groupListByKey(lists:Collection<PersonalizedList>): Array<AnyJson>{
-    const {app:{i18n}} = useContext()
-    let listsGroupByKeyMenu:any = {}
-    for(const list of lists){
-      const key = i18n.t(list.menuKey) as string
-      listsGroupByKeyMenu[key] = listsGroupByKeyMenu[key] ?? []
-      listsGroupByKeyMenu[key].push(list)
-    }
-    return listsGroupByKeyMenu
+function groupListByKey (lists:Collection<PersonalizedList>): Array<AnyJson> {
+  const { app: { i18n } } = useContext()
+  const listsGroupByKeyMenu:any = {}
+  for (const list of lists) {
+    const key = i18n.t(list.menuKey) as string
+    listsGroupByKeyMenu[key] = listsGroupByKeyMenu[key] ?? []
+    listsGroupByKeyMenu[key].push(list)
   }
+  return listsGroupByKeyMenu
+}
 
-  /**
+/**
    * On construit la list d'Item, constituée de Header et d'Item "sélectionnable"
    * @param listsGroupByKeyMenu
    */
-  function constructAutoCompleteItems(listsGroupByKeyMenu:Array<any>){
-    const items: any = []
-    for(const key in listsGroupByKeyMenu){
-      items.push({ header: key })
-      for(const item of listsGroupByKeyMenu[key]){
-        items.push(item)
-      }
+function constructAutoCompleteItems (listsGroupByKeyMenu:Array<any>) {
+  const items: any = []
+  for (const key in listsGroupByKeyMenu) {
+    items.push({ header: key })
+    for (const item of listsGroupByKeyMenu[key]) {
+      items.push(item)
     }
-    return items
   }
+  return items
+}
 </script>
 
-
 <style scoped>
 
 </style>

+ 18 - 14
components/Layout/Subheader.vue

@@ -1,3 +1,8 @@
+<!--
+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
@@ -5,36 +10,35 @@
       flat
       tile
     >
-      <LayoutSubHeaderBreadcrumbs class="mr-auto"></LayoutSubHeaderBreadcrumbs>
+      <LayoutSubHeaderBreadcrumbs class="mr-auto" />
 
       <v-card
         class="d-md-flex ot_super_light_grey pt-2 mr-6  align-baseline"
         flat
         tile
       >
-        <LayoutSubHeaderActivityYear class="activity-year" v-if="!showDateTimeRange"></LayoutSubHeaderActivityYear>
-        <LayoutSubHeaderDataTiming v-if="!showDateTimeRange" class="data-timing ml-2"></LayoutSubHeaderDataTiming>
-        <LayoutSubHeaderDataTimingRange class="data-timing-range ml-n1" v-on:showDateTimeRange="showDateTimeRange=$event"></LayoutSubHeaderDataTimingRange>
-        <LayoutSubHeaderPersonnalizedList class="personalized-list ml-2"></LayoutSubHeaderPersonnalizedList>
+        <LayoutSubHeaderActivityYear v-if="!showDateTimeRange" class="activity-year" />
+        <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" />
       </v-card>
     </v-card>
   </main>
 </template>
 
 <script lang="ts">
-  import {defineComponent, ref, Ref} from '@nuxtjs/composition-api'
+import { defineComponent, ref, Ref } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    setup() {
-      const showDateTimeRange:Ref<boolean> = ref(false)
-      return {
-        showDateTimeRange
-      }
+export default defineComponent({
+  setup () {
+    const showDateTimeRange: Ref<boolean> = ref(false)
+    return {
+      showDateTimeRange
     }
-  })
+  }
+})
 </script>
 
-
 <style scoped>
 
 </style>

+ 43 - 40
components/Ui/Button/Delete.vue

@@ -1,3 +1,7 @@
+<!--
+Bouton Delete avec modale de confirmation de la suppression
+-->
+
 <template>
   <main>
     <v-btn icon @click="alertDeleteItem()">
@@ -9,19 +13,19 @@
     >
       <template v-slot:dialogText>
         <v-card-title class="text-h5 grey lighten-2">
-          {{$t('attention')}}
+          {{ $t('attention') }}
         </v-card-title>
         <v-card-text>
           <br>
-          <p>{{$t('confirm_to_delete')}}</p>
+          <p>{{ $t('confirm_to_delete') }}</p>
         </v-card-text>
       </template>
       <template v-slot:dialogBtn>
         <v-btn class="mr-4 submitBtn ot_grey ot_white--text" @click="closeDialog">
-          {{$t('cancel')}}
+          {{ $t('cancel') }}
         </v-btn>
         <v-btn class="mr-4 submitBtn ot_danger ot_white--text" @click="deleteItem">
-          {{$t('delete')}}
+          {{ $t('delete') }}
         </v-btn>
       </template>
     </lazy-LayoutDialog>
@@ -29,48 +33,47 @@
 </template>
 
 <script lang="ts">
-  import {defineComponent, useContext, ref, Ref} from '@nuxtjs/composition-api'
-  import {DataDeleterArgs} from "~/types/interfaces";
-  import {TYPE_ALERT} from "~/types/enums";
-  import {alert} from "~/types/interfaces";
+import { defineComponent, useContext, ref, Ref } from '@nuxtjs/composition-api'
+import { DataDeleterArgs, alert } from '~/types/interfaces'
+import { TYPE_ALERT } from '~/types/enums'
 
-  export default defineComponent({
-    props: {
-      deleteArgs: {
-        type: Object as () => DataDeleterArgs,
-        required: true
-      }
-    },
-    setup(props){
-      const {$dataDeleter, store, app: {i18n}} = useContext()
-      const showDialog:Ref<boolean> = ref(false)
+export default defineComponent({
+  props: {
+    deleteArgs: {
+      type: Object as () => DataDeleterArgs,
+      required: true
+    }
+  },
+  setup (props) {
+    const { $dataDeleter, store, app: { i18n } } = useContext()
+    const showDialog: Ref<boolean> = ref(false)
 
-      const deleteItem = async () =>{
-        try{
-          await $dataDeleter.invoke(props.deleteArgs)
-          const alert:alert = {
-            type: TYPE_ALERT.SUCCESS,
-            message: i18n.t('deleteSuccess') as string
-          }
-          store.commit('page/setAlert', alert)
-        }catch (error) {
-          const alert:alert = {
-            type: TYPE_ALERT.ALERT,
-            message: error.message
-          }
-          store.commit('page/setAlert', alert)
+    const deleteItem = async () => {
+      try {
+        await $dataDeleter.invoke(props.deleteArgs)
+        const alert: alert = {
+          type: TYPE_ALERT.SUCCESS,
+          message: i18n.t('deleteSuccess') as string
         }
-        showDialog.value = false
+        store.commit('page/setAlert', alert)
+      } catch (error) {
+        const alert: alert = {
+          type: TYPE_ALERT.ALERT,
+          message: error.message
+        }
+        store.commit('page/setAlert', alert)
       }
+      showDialog.value = false
+    }
 
-      return {
-        alertDeleteItem : () => showDialog.value = true,
-        closeDialog : () => showDialog.value = false,
-        deleteItem,
-        showDialog
-      }
+    return {
+      alertDeleteItem: () => { showDialog.value = true },
+      closeDialog: () => { showDialog.value = false },
+      deleteItem,
+      showDialog
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 39 - 34
components/Ui/Card.vue

@@ -1,3 +1,7 @@
+<!--
+Container de type Card
+-->
+
 <template>
   <v-card
     elevation="2"
@@ -6,55 +10,56 @@
     min-height="200"
   >
     <v-card-title>
-      <slot name="card.title"></slot>
+      <slot name="card.title" />
     </v-card-title>
     <v-card-text>
-      <slot name="card.text"></slot>
+      <slot name="card.text" />
     </v-card-text>
     <v-card-actions>
-      <v-spacer></v-spacer>
+      <v-spacer />
       <v-btn icon>
-        <NuxtLink :to="link" class="no-decoration"><v-icon>mdi-pencil</v-icon></NuxtLink>
+        <NuxtLink :to="link" class="no-decoration">
+          <v-icon>mdi-pencil</v-icon>
+        </NuxtLink>
       </v-btn>
-      <UiButtonDelete :deleteArgs="args"></UiButtonDelete>
-      <slot name="card.action"></slot>
+      <UiButtonDelete :delete-args="args" />
+      <slot name="card.action" />
     </v-card-actions>
   </v-card>
 </template>
 
 <script lang="ts">
-  import {defineComponent} from '@nuxtjs/composition-api'
-  import {QUERY_TYPE} from "~/types/enums";
-  import {DataDeleterArgs} from "~/types/interfaces";
+import { defineComponent } from '@nuxtjs/composition-api'
+import { QUERY_TYPE } from '~/types/enums'
+import { DataDeleterArgs } from '~/types/interfaces'
 
-  export default defineComponent({
-    props: {
-      link:{
-        type: String,
-        required: true
-      },
-      model:{
-        type: Function,
-        required: true
-      },
-      id:{
-        type: Number,
-        required: true
-      }
+export default defineComponent({
+  props: {
+    link: {
+      type: String,
+      required: true
+    },
+    model: {
+      type: Function,
+      required: true
     },
-    setup(props) {
-      const args: DataDeleterArgs = {
-        type: QUERY_TYPE.MODEL,
-        model: props.model,
-        id: props.id
-      }
-      return {
-        args
-      }
+    id: {
+      type: Number,
+      required: true
+    }
+  },
+  setup (props) {
+    const args: DataDeleterArgs = {
+      type: QUERY_TYPE.MODEL,
+      model: props.model,
+      id: props.id
     }
-  })
+    return {
+      args
+    }
+  }
+})
 </script>
 
-
 <style scoped>
 </style>

+ 97 - 93
components/Ui/DataTable.vue

@@ -1,112 +1,116 @@
-<template>
-    <v-col
-      cols="12"
-      sm="12"
-    >
-      <v-data-table
-        :headers="headersWithItem"
-        :items="entries"
-        :server-items-length="totalEntries"
-        :loading="$fetchState.pending"
-        class="elevation-1"
-      >
+<!--
+Tableau interactif
 
-        <template v-for="header in headersWithItem" v-slot:[header.item]="props">
-          <slot :name="header.item" v-bind="props">
-            {{props.item[header.value]}}
-          </slot>
-        </template>
+@see https://vuetifyjs.com/en/components/data-tables/
+-->
 
-        <template v-slot:item.actions="{ item }">
-          <v-icon
-            small
-            class="mr-2"
-            @click="editItem(item)"
-          >
-            mdi-pencil
-          </v-icon>
-          <v-icon
-            small
-            @click="deleteItem(item)"
-          >
-            mdi-delete
-          </v-icon>
-        </template>
+<template>
+  <v-col
+    cols="12"
+    sm="12"
+  >
+    <v-data-table
+      :headers="headersWithItem"
+      :items="entries"
+      :server-items-length="totalEntries"
+      :loading="$fetchState.pending"
+      class="elevation-1"
+    >
+      <template v-for="header in headersWithItem" v-slot:[header.item]="props">
+        <slot :name="header.item" v-bind="props">
+          {{ props.item[header.value] }}
+        </slot>
+      </template>
 
-      </v-data-table>
-    </v-col>
+      <template v-slot:item.actions="{ item }">
+        <v-icon
+          small
+          class="mr-2"
+          @click="editItem(item)"
+        >
+          mdi-pencil
+        </v-icon>
+        <v-icon
+          small
+          @click="deleteItem(item)"
+        >
+          mdi-delete
+        </v-icon>
+      </template>
+    </v-data-table>
+  </v-col>
 </template>
 
 <script lang="ts">
-  import {defineComponent, ref, useContext, useFetch, computed, toRefs, Ref} from '@nuxtjs/composition-api'
-  import {Query} from "@vuex-orm/core";
-  import {AnyJson} from "~/types/interfaces";
-  import {queryHelper} from "~/services/store/query";
-  import {QUERY_TYPE} from "~/types/enums";
+import { defineComponent, ref, useContext, useFetch, computed, toRefs, Ref } from '@nuxtjs/composition-api'
+import { Query } from '@vuex-orm/core'
+import { AnyJson } from '~/types/interfaces'
+import { queryHelper } from '~/services/store/query'
+import { QUERY_TYPE } from '~/types/enums'
 
-  export default defineComponent({
-    props: {
-      rootModel:{
-        type: Function,
-        required: true
-      },
-      rootId:{
-        type: Number,
-        required: true
-      },
-      model:{
-        type: Function,
-        required: true
-      },
-      query:{
-        type: Object as () => Query,
-        required: true
-      },
-      headers:{
-        type: Array,
-        required: true
-      }
+export default defineComponent({
+  props: {
+    rootModel: {
+      type: Function,
+      required: true
+    },
+    rootId: {
+      type: Number,
+      required: true
     },
-    setup(props) {
-      const {rootModel, rootId, model, headers, query} = toRefs(props);
+    model: {
+      type: Function,
+      required: true
+    },
+    query: {
+      type: Object as () => Query,
+      required: true
+    },
+    headers: {
+      type: Array,
+      required: true
+    }
+  },
+  setup (props) {
+    const { rootModel, rootId, model, headers, query } = toRefs(props)
 
-      const headersWithItem = computed(()=>{
-        return headers.value.map((header:any)=>{
-          header['item'] = 'item.' + header.value
-          return header
-        })
+    const headersWithItem = computed(() => {
+      return headers.value.map((header:any) => {
+        header.item = 'item.' + header.value
+        return header
       })
+    })
 
-      const totalEntries:Ref<number> = ref(0)
-      const entries:Ref<Array<AnyJson>> = ref(Array<AnyJson>())
+    const totalEntries:Ref<number> = ref(0)
+    const entries:Ref<Array<AnyJson>> = ref(Array<AnyJson>())
 
-      const {$dataProvider} = useContext()
-      useFetch(async ()=>{
-        await $dataProvider.invoke({
-          type: QUERY_TYPE.MODEL,
-          model: model.value,
-          root_model: rootModel.value,
-          root_id: rootId.value
-        })
-        entries.value = queryHelper.getFlattenEntries(query.value);
-        totalEntries.value = entries.value.length
+    const { $dataProvider } = useContext()
+    useFetch(async () => {
+      await $dataProvider.invoke({
+        type: QUERY_TYPE.MODEL,
+        model: model.value,
+        rootModel: rootModel.value,
+        rootId: rootId.value
       })
+      entries.value = queryHelper.getFlattenEntries(query.value)
+      totalEntries.value = entries.value.length
+    })
 
-      const itemId:Ref<number> = ref(0);
+    const itemId:Ref<number> = ref(0)
 
-      const editItem = (item: AnyJson) => {
-        itemId.value=item.id;
-      }
+    const editItem = (item: AnyJson) => {
+      itemId.value = item.id
+    }
 
-      // onUnmounted( useRepositoryHelper.cleanRepository(repository.value) )
+    // onUnmounted( useRepositoryHelper.cleanRepository(repository.value) )
 
-      return {
-        entries,
-        totalEntries,
-        headersWithItem,
-        editItem,
-        itemId
-      }
+    return {
+      entries,
+      totalEntries,
+      headersWithItem,
+      editItem,
+      itemId
     }
-  })
+  }
+})
 </script>

+ 32 - 23
components/Ui/ExpansionPanel.vue

@@ -1,28 +1,37 @@
+<!--
+Panneaux déroulants de type "accordéon"
+
+@see https://vuetifyjs.com/en/components/expansion-panels/
+-->
+
 <template>
-     <v-expansion-panel :id="id" >
-       <v-expansion-panel-header class="ot_light_grey">
-         <v-icon class="ot_white--text ot_green icon">{{icon}}</v-icon>
-         {{$t(id)}}
-       </v-expansion-panel-header>
-       <v-expansion-panel-content>
-        <slot></slot>
-       </v-expansion-panel-content>
-     </v-expansion-panel>
- </template>
+  <v-expansion-panel :id="id">
+    <v-expansion-panel-header class="ot_light_grey">
+      <v-icon class="ot_white--text ot_green icon">
+        {{ icon }}
+      </v-icon>
+      {{ $t(id) }}
+    </v-expansion-panel-header>
+    <v-expansion-panel-content>
+      <slot />
+    </v-expansion-panel-content>
+  </v-expansion-panel>
+</template>
 
 <script lang="ts">
-    export default {
-      props: {
-        id:{
-          type: String,
-          required: true
-        },
-        icon:{
-          type: String,
-          required: false
-        }
-      }
+export default {
+  props: {
+    id: {
+      type: String,
+      required: true
+    },
+    icon: {
+      type: String,
+      required: false,
+      default: null
     }
+  }
+}
 </script>
 
 <style scoped>
@@ -31,10 +40,10 @@
     height: 47px;
     padding: 10px;
     margin-right: 10px;
-    flex: none;
+    flex: none !important;
   }
   .v-expansion-panel-header{
-    padding: 0px;
+    padding: 0;
   }
   .v-expansion-panel--active > .v-expansion-panel-header{
     min-height: 47px;

+ 113 - 107
components/Ui/Form.vue

@@ -1,3 +1,9 @@
+<!--
+Formulaire générique
+
+@see https://vuetifyjs.com/en/components/forms/#usage
+-->
+
 <template>
   <main>
     <v-form
@@ -9,22 +15,22 @@
       <v-container fluid class="container btnActions">
         <v-row>
           <v-col cols="12" sm="12">
-            <slot name="form.button"></slot>
+            <slot name="form.button" />
             <v-btn v-if="!readOnly" class="mr-4 ot_green ot_white--text" @click="submit">
-              {{$t('save')}}
+              {{ $t('save') }}
             </v-btn>
           </v-col>
         </v-row>
       </v-container>
 
-      <slot name="form.input" v-bind="{entry,updateRepository}"></slot>
+      <slot name="form.input" v-bind="{entry,updateRepository}" />
 
       <v-container fluid class="container btnActions">
         <v-row>
           <v-col cols="12" sm="12">
-            <slot name="form.button"></slot>
+            <slot name="form.button" />
             <v-btn v-if="!readOnly" class="mr-4 ot_green ot_white--text" @click="submit">
-              {{$t('save')}}
+              {{ $t('save') }}
             </v-btn>
           </v-col>
         </v-row>
@@ -36,22 +42,22 @@
     >
       <template v-slot:dialogText>
         <v-card-title class="text-h5 grey lighten-2">
-          {{$t('attention')}}
+          {{ $t('attention') }}
         </v-card-title>
         <v-card-text>
           <br>
-          <p>{{$t('quit_without_saving_warning')}}</p>
+          <p>{{ $t('quit_without_saving_warning') }}</p>
         </v-card-text>
       </template>
       <template v-slot:dialogBtn>
         <v-btn class="mr-4 submitBtn ot_green ot_white--text" @click="closeDialog">
-          {{$t('back_to_form')}}
+          {{ $t('back_to_form') }}
         </v-btn>
         <v-btn class="mr-4 submitBtn ot_green ot_white--text" @click="saveAndQuit">
-          {{$t('save_and_quit')}}
+          {{ $t('save_and_quit') }}
         </v-btn>
         <v-btn class="mr-4 submitBtn ot_danger ot_white--text" @click="goEvenUnsavedData">
-          {{$t('quit_form')}}
+          {{ $t('quit_form') }}
         </v-btn>
       </template>
     </lazy-LayoutDialog>
@@ -59,116 +65,116 @@
 </template>
 
 <script lang="ts">
-  import {computed, defineComponent, onBeforeMount, onBeforeUnmount, onUnmounted, reactive, toRefs, useContext, watch, ref, Ref, ComputedRef} from '@nuxtjs/composition-api'
-  import {repositoryHelper} from "~/services/store/repository";
-  import {queryHelper} from "~/services/store/query";
-  import {Query} from "@vuex-orm/core";
-  import {QUERY_TYPE, TYPE_ALERT} from "~/types/enums";
-  import {alert, AnyJson} from "~/types/interfaces";
-  import {$useDirtyForm} from "~/use/form/useDirtyForm";
-  import {ToRefs, UnwrapRef} from "@vue/composition-api";
-
-  export default defineComponent({
-    props: {
-      model: {
-        type: Function,
-        required: true
-      },
-      id: {
-        type: Number,
-        required: true
-      },
-      query: {
-        type: Object as () => Query,
-        required: true
-      },
+import {
+  computed, defineComponent, reactive, toRefs, useContext, ref, Ref, ComputedRef, ToRefs, UnwrapRef
+} from '@nuxtjs/composition-api'
+import { Query } from '@vuex-orm/core'
+import { repositoryHelper } from '~/services/store/repository'
+import { queryHelper } from '~/services/store/query'
+import { QUERY_TYPE, TYPE_ALERT } from '~/types/enums'
+import { alert, AnyJson } from '~/types/interfaces'
+import { $useDirtyForm } from '~/use/form/useDirtyForm'
+
+export default defineComponent({
+  props: {
+    model: {
+      type: Function,
+      required: true
     },
-    setup: function (props) {
-      const {$dataPersister, store, app: {router, i18n}} = useContext()
-      const {markFormAsDirty, markFormAsNotDirty} = $useDirtyForm(store)
-
-      const {id, query}:ToRefs = toRefs(props)
-      const properties:UnwrapRef<AnyJson> = reactive({
-        valid: false,
-        saveOk: false,
-        saveKo: false
-      })
-
-      const readOnly:Ref<boolean> = ref(false)
-
-      const entry:ComputedRef<AnyJson> = computed(() => {
-        return queryHelper.getFlattenEntry(query.value, id.value)
-      })
-
-      const updateRepository = (newValue: string, field: string) => {
-        markFormAsDirty()
-        repositoryHelper.updateStoreFromField(props.model, entry.value, newValue, field)
-      }
+    id: {
+      type: Number,
+      required: true
+    },
+    query: {
+      type: Object as () => Query,
+      required: true
+    }
+  },
+  setup (props) {
+    const { $dataPersister, store, app: { router, i18n } } = useContext()
+    const { markFormAsDirty, markFormAsNotDirty } = $useDirtyForm(store)
+
+    const { id, query }:ToRefs = toRefs(props)
+    const properties:UnwrapRef<AnyJson> = reactive({
+      valid: false,
+      saveOk: false,
+      saveKo: false
+    })
+
+    const readOnly:Ref<boolean> = ref(false)
+
+    const entry:ComputedRef<AnyJson> = computed(() => {
+      return queryHelper.getFlattenEntry(query.value, id.value)
+    })
+
+    const updateRepository = (newValue: string, field: string) => {
+      markFormAsDirty()
+      repositoryHelper.updateStoreFromField(props.model, entry.value, newValue, field)
+    }
 
-      const submit = async () => {
-        try {
-          markFormAsNotDirty()
-          await $dataPersister.invoke({
-            type: QUERY_TYPE.MODEL,
-            model: props.model,
-            id: id.value
-          })
-
-          const alert:alert = {
-            type: TYPE_ALERT.SUCCESS,
-            message: i18n.t('saveSuccess') as string
-          }
-          store.commit('page/setAlert', alert)
-
-        } catch (error) {
-          const alert:alert = {
-            type: TYPE_ALERT.ALERT,
-            message: error.message
-          }
-          store.commit('page/setAlert', alert)
+    const submit = async () => {
+      try {
+        markFormAsNotDirty()
+        await $dataPersister.invoke({
+          type: QUERY_TYPE.MODEL,
+          model: props.model,
+          id: id.value
+        })
+
+        const alert:alert = {
+          type: TYPE_ALERT.SUCCESS,
+          message: i18n.t('saveSuccess') as string
+        }
+        store.commit('page/setAlert', alert)
+      } catch (error) {
+        const alert:alert = {
+          type: TYPE_ALERT.ALERT,
+          message: error.message
         }
+        store.commit('page/setAlert', alert)
       }
+    }
 
-      const showDialog:ComputedRef<boolean> = computed(() => {
-        return store.state.form.showConfirmToLeave
-      })
-
-      const closeDialog = () => {
-        store.commit('form/setShowConfirmToLeave', false)
-      }
+    const showDialog:ComputedRef<boolean> = computed(() => {
+      return store.state.form.showConfirmToLeave
+    })
 
-      const saveAndQuit = async () => {
-        await submit()
-        goEvenUnsavedData()
-      }
+    const closeDialog = () => {
+      store.commit('form/setShowConfirmToLeave', false)
+    }
 
-      const goEvenUnsavedData = () => {
-        markFormAsNotDirty()
-        store.commit('form/setShowConfirmToLeave', false)
+    const saveAndQuit = async () => {
+      await submit()
+      goEvenUnsavedData()
+    }
 
-        const entryCopy = query.value.first()
-        if (entryCopy && entryCopy.$getAttributes()['originalState']) {
-          repositoryHelper.persist(props.model, entryCopy.$getAttributes()['originalState'])
-        }
+    const goEvenUnsavedData = () => {
+      markFormAsNotDirty()
+      store.commit('form/setShowConfirmToLeave', false)
 
-        if (router) {
-          router.push(store.state.form.goAfterLeave)
-        }
+      const entryCopy = query.value.first()
+      if (entryCopy && entryCopy.$getAttributes().originalState) {
+        repositoryHelper.persist(props.model, entryCopy.$getAttributes().originalState)
       }
 
-      return {
-        submit,
-        updateRepository,
-        properties,
-        readOnly,
-        showDialog,
-        entry,
-        goEvenUnsavedData,
-        closeDialog,
-        saveAndQuit
+      if (router) {
+        router.push(store.state.form.goAfterLeave)
       }
     }
-  })
+
+    return {
+      submit,
+      updateRepository,
+      properties,
+      readOnly,
+      showDialog,
+      entry,
+      goEvenUnsavedData,
+      closeDialog,
+      saveAndQuit
+    }
+  }
+})
 </script>
 
 <style scoped>

+ 71 - 64
components/Ui/Input/Autocomplete.vue

@@ -1,7 +1,12 @@
+<!--
+Liste déroulante avec autocompletion
+
+@see https://vuetifyjs.com/en/components/autocompletes/#usage
+-->
+
 <template>
   <main>
     <v-autocomplete
-      @change="$emit('update', $event, field)"
       :items="itemsToDisplayed"
       :label="$t(label)"
       item-text="textDisplay"
@@ -9,78 +14,80 @@
       :multiple="multiple"
       :loading="isLoading"
       :return-object="returnObject"
-    >
-    </v-autocomplete>
+      @change="$emit('update', $event, field)"
+    />
   </main>
 </template>
 
 <script lang="ts">
-  import {computed, defineComponent, ComputedRef} from '@nuxtjs/composition-api'
-  import {AnyJson} from "~/types/interfaces";
+import { computed, defineComponent, ComputedRef } from '@nuxtjs/composition-api'
+import { AnyJson } from '~/types/interfaces'
 
-  export default defineComponent({
-    props: {
-      label: {
-        type: String,
-        required: false
-      },
-      field: {
-        type: String,
-        required: false
-      },
-      data: {
-        type: String,
-        required: false
-      },
-      items: {
-        type: Array,
-        required: false,
-        default: []
-      },
-      readOnly: {
-        type: Boolean,
-        required: false
-      },
-      itemValue: {
-        type: String,
-        default: 'id'
-      },
-      itemText: {
-        type: Array,
-        required: true
-      },
-      returnObject: {
-        type: Boolean,
-        default: false
-      },
-      multiple: {
-        type: Boolean,
-        default: false
-      },
-      isLoading: {
-        type: Boolean,
-        default: false
-      }
+export default defineComponent({
+  props: {
+    label: {
+      type: String,
+      required: false,
+      default: ''
+    },
+    field: {
+      type: String,
+      required: false,
+      default: null
+    },
+    data: {
+      type: String,
+      required: false,
+      default: null
+    },
+    items: {
+      type: Array,
+      required: false,
+      default: () => []
     },
-    setup(props) {
-      //On reconstruit les items à afficher car le text de l'Item doit être construit par rapport au itemText passé en props
-      const itemsToDisplayed: ComputedRef<Array<AnyJson>> = computed(() => {
-        return props.items.map((item: any) => {
-          const textDisplay: Array<string> = []
-          for (const text of props.itemText) {
-            textDisplay.push(item[text as string])
-          }
-          return Object.assign({}, item, {textDisplay: textDisplay.join(' ')})
-        })
+    readOnly: {
+      type: Boolean,
+      required: false
+    },
+    itemValue: {
+      type: String,
+      default: 'id'
+    },
+    itemText: {
+      type: Array,
+      required: true
+    },
+    returnObject: {
+      type: Boolean,
+      default: false
+    },
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    isLoading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup (props) {
+    // On reconstruit les items à afficher car le text de l'Item doit être construit par rapport au itemText passé en props
+    const itemsToDisplayed: ComputedRef<Array<AnyJson>> = computed(() => {
+      return props.items.map((item: any) => {
+        const textDisplay: Array<string> = []
+        for (const text of props.itemText) {
+          textDisplay.push(item[text as string])
+        }
+        return Object.assign({}, item, { textDisplay: textDisplay.join(' ') })
       })
+    })
 
-      return {
-        label_field: props.label ?? props.field,
-        itemsToDisplayed
-      }
-
+    return {
+      label_field: props.label ?? props.field,
+      itemsToDisplayed
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 83 - 75
components/Ui/Input/AutocompleteWithAPI.vue

@@ -1,3 +1,10 @@
+<!--
+Liste déroulante avec autocompletion (les données sont issues
+de l'api Opentalent)
+
+@see https://vuetifyjs.com/en/components/autocompletes/#usage
+-->
+
 <template>
   <main>
     <v-autocomplete
@@ -14,97 +21,98 @@
       :placeholder="$t('start_your_research')"
       prepend-icon="mdi-magnify"
       :return-object="returnObject"
-    ></v-autocomplete>
-
+    />
   </main>
 </template>
 
 <script lang="ts">
-  import {defineComponent, computed, watch, ref, useContext, onUnmounted, Ref} from '@nuxtjs/composition-api'
-  import {QUERY_TYPE} from "~/types/enums";
-  import * as _ from "lodash";
+import { defineComponent, computed, watch, ref, useContext, onUnmounted, Ref } from '@nuxtjs/composition-api'
+import * as _ from 'lodash'
+import { QUERY_TYPE } from '~/types/enums'
 
-  export default defineComponent({
-    props: {
-      label:{
-        type: String,
-        required: false
-      },
-      field:{
-        type: String,
-        required: false
-      },
-      data: {
-        type: String,
-        required: false
-      },
-      readOnly: {
-        type: Boolean,
-        required: false
-      },
-      itemValue:{
-        type: String,
-        default: 'id'
-      },
-      itemText:{
-        type: Array,
-        required: true
-      },
-      returnObject:{
-        type: Boolean,
-        default: false
-      }
+export default defineComponent({
+  props: {
+    label: {
+      type: String,
+      required: false,
+      default: ''
+    },
+    field: {
+      type: String,
+      required: false,
+      default: null
+    },
+    data: {
+      type: String,
+      required: false,
+      default: null
+    },
+    readOnly: {
+      type: Boolean,
+      required: false
     },
-    setup(props){
-      const {$dataProvider} = useContext();
+    itemValue: {
+      type: String,
+      default: 'id'
+    },
+    itemText: {
+      type: Array,
+      required: true
+    },
+    returnObject: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup (props) {
+    const { $dataProvider } = useContext()
 
-      const search:Ref<string|null> = ref(null);
-      const model = ref(null);
-      const count = ref(0);
-      const entries = ref([]);
-      const isLoading = ref(false);
+    const search:Ref<string|null> = ref(null)
+    const model = ref(null)
+    const count = ref(0)
+    const entries = ref([])
+    const isLoading = ref(false)
 
-      const items = computed(() => {
-        return entries.value.map(entry => {
-          const textDisplay:Array<string> = []
-          for (const text of props.itemText){
-            textDisplay.push(entry[text as string])
-          }
-          return Object.assign({}, entry, { textDisplay: textDisplay.join(' ') })
-        })
+    const items = computed(() => {
+      return entries.value.map((entry) => {
+        const textDisplay:Array<string> = []
+        for (const text of props.itemText) {
+          textDisplay.push(entry[text as string])
+        }
+        return Object.assign({}, entry, { textDisplay: textDisplay.join(' ') })
       })
+    })
 
-      const unwatch = watch(search, _.debounce(async (research) => {
-        // Items have already been requested
-        if (isLoading.value) return
+    const unwatch = watch(search, _.debounce(async (research) => {
+      // Items have already been requested
+      if (isLoading.value) { return }
 
-        isLoading.value = true
+      isLoading.value = true
 
-        let response = await $dataProvider.invoke({
-          type: QUERY_TYPE.DEFAULT,
-          url: `gps-coordinate-searching?city=${research}`
-        })
-
-        count.value = response.length
-        entries.value = response
-        isLoading.value = false
-      }, 500))
-
-      onUnmounted(()=>{
-        unwatch()
+      const response = await $dataProvider.invoke({
+        type: QUERY_TYPE.DEFAULT,
+        url: `gps-coordinate-searching?city=${research}`
       })
 
-      return {
-        label_field : props.label ?? props.field,
-        count,
-        isLoading,
-        items,
-        search,
-        model
-      }
+      count.value = response.length
+      entries.value = response
+      isLoading.value = false
+    }, 500))
+
+    onUnmounted(() => {
+      unwatch()
+    })
 
+    return {
+      label_field: props.label ?? props.field,
+      count,
+      isLoading,
+      items,
+      search,
+      model
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 34 - 26
components/Ui/Input/Checkbox.vue

@@ -1,3 +1,9 @@
+<!--
+Case à cocher
+
+@see https://vuetifyjs.com/en/components/checkboxes/
+-->
+
 <template>
   <v-container
     class="px-0"
@@ -6,40 +12,42 @@
     <v-checkbox
       :value="data"
       :label="$t(label_field)"
-      @change="$emit('update', $event, field)"
       :disabled="readOnly"
-    ></v-checkbox>
+      @change="$emit('update', $event, field)"
+    />
   </v-container>
 </template>
 
 <script lang="ts">
-  import {defineComponent} from '@nuxtjs/composition-api'
+import { defineComponent } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    props: {
-      field: {
-        type: String,
-        required: false
-      },
-      label: {
-        type: String,
-        required: false
-      },
-      data: {
-        type: Boolean,
-        required: false
-      },
-      readOnly: {
-        type: Boolean,
-        required: false
-      },
+export default defineComponent({
+  props: {
+    field: {
+      type: String,
+      required: false,
+      default: null
     },
-    setup(props){
-      return {
-        label_field : props.label ?? props.field,
-      }
+    label: {
+      type: String,
+      required: false,
+      default: ''
+    },
+    data: {
+      type: Boolean,
+      required: false
+    },
+    readOnly: {
+      type: Boolean,
+      required: false
+    }
+  },
+  setup (props) {
+    return {
+      label_field: props.label ?? props.field
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 75 - 68
components/Ui/Input/DatePicker.vue

@@ -1,3 +1,9 @@
+<!--
+Sélecteur de dates
+
+@see https://vuetifyjs.com/en/components/date-pickers/
+-->
+
 <template>
   <main>
     <v-menu
@@ -16,97 +22,98 @@
           prepend-icon="mdi-calendar"
           :disabled="readOnly"
           v-bind="attrs"
-          v-on="on"
           :dense="dense"
           :single-line="singleLine"
-        ></v-text-field>
+          v-on="on"
+        />
       </template>
       <v-date-picker
         v-model="datesParsed"
-        @input="dateOpen = range && datesParsed.length < 2"
         locale="fr"
         :range="range"
         color="ot_green lighten-1"
-      ></v-date-picker>
+        @input="dateOpen = range && datesParsed.length < 2"
+      />
     </v-menu>
   </main>
 </template>
 
-
 <script lang="ts">
-  import {defineComponent, watch, ref, useContext, onUnmounted, computed, Ref, ComputedRef} from '@nuxtjs/composition-api'
-  import DatesUtils from "~/services/utils/datesUtils";
-  import {WatchStopHandle} from "@vue/composition-api";
+import { defineComponent, watch, ref, useContext, onUnmounted, computed, Ref, ComputedRef } from '@nuxtjs/composition-api'
+import { WatchStopHandle } from '@vue/composition-api'
+import DatesUtils from '~/services/utils/datesUtils'
 
-  export default defineComponent({
-    props: {
-      field: {
-        type: String,
-        required: false
-      },
-      label: {
-        type: String,
-        required: false
-      },
-      data: {
-        type: [String, Array],
-        required: false
-      },
-      readOnly: {
-        type: Boolean,
-        required: false
-      },
-      range: {
-        type: Boolean,
-        required: false
-      },
-      dense: {
-        type: Boolean,
-        required: false
-      },
-      singleLine: {
-        type: Boolean,
-        required: false
-      }
+export default defineComponent({
+  props: {
+    field: {
+      type: String,
+      required: false,
+      default: null
     },
-    setup(props, {emit}){
-      const {data, field, range} = props
-      const {$moment} = useContext()
-      const dateUtils = new DatesUtils($moment)
+    label: {
+      type: String,
+      required: false,
+      default: ''
+    },
+    data: {
+      type: [String, Array],
+      required: false,
+      default: null
+    },
+    readOnly: {
+      type: Boolean,
+      required: false
+    },
+    range: {
+      type: Boolean,
+      required: false
+    },
+    dense: {
+      type: Boolean,
+      required: false
+    },
+    singleLine: {
+      type: Boolean,
+      required: false
+    }
+  },
+  setup (props, { emit }) {
+    const { data, field, range } = props
+    const { $moment } = useContext()
+    const dateUtils = new DatesUtils($moment)
 
-      const datesParsed:Ref<Array<string>|string> = range ? ref(Array<string>()) : ref('')
+    const datesParsed: Ref<Array<string>|string> = range ? ref(Array<string>()) : ref('')
 
-      if(datesParsed.value instanceof Array){
-        for(const date of data as Array<string>){
-          if(date)
-            datesParsed.value.push($moment(date).format('YYYY-MM-DD'))
-        }
-      }else{
-        datesParsed.value = $moment(data as string).format('YYYY-MM-DD')
+    if (Array.isArray(datesParsed.value)) {
+      for (const date of data as Array<string>) {
+        if (date) { datesParsed.value.push($moment(date).format('YYYY-MM-DD')) }
       }
+    } else {
+      datesParsed.value = $moment(data as string).format('YYYY-MM-DD')
+    }
 
-      const datesFormatted:ComputedRef<string> = computed(()=>{
-       return dateUtils.formattedDate(datesParsed.value, 'DD/MM/YYYY')
-      })
+    const datesFormatted: ComputedRef<string> = computed(() => {
+      return dateUtils.formattedDate(datesParsed.value, 'DD/MM/YYYY')
+    })
 
-      const unwatch:WatchStopHandle = watch(datesParsed, (newValue, oldValue) => {
-        if(newValue === oldValue) return
-        if(newValue instanceof Array && newValue.length < 2) return
-        emit('update', newValue instanceof Array ? dateUtils.sortDate(newValue) : newValue, field)
-      })
+    const unwatch: WatchStopHandle = watch(datesParsed, (newValue, oldValue) => {
+      if (newValue === oldValue) { return }
+      if (Array.isArray(newValue) && newValue.length < 2) { return }
+      emit('update', Array.isArray(newValue) ? dateUtils.sortDate(newValue) : newValue, field)
+    })
 
-      onUnmounted(()=>{
-        unwatch()
-      })
+    onUnmounted(() => {
+      unwatch()
+    })
 
-      return {
-        label_field : props.label ?? props.field,
-        datesParsed,
-        datesFormatted,
-        dateOpen: ref(false)
-      }
+    return {
+      label_field: props.label ?? props.field,
+      datesParsed,
+      datesFormatted,
+      dateOpen: ref(false)
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 57 - 47
components/Ui/Input/Enum.vue

@@ -1,10 +1,16 @@
+<!--
+Liste déroulante dédiée à l'affichage d'objets Enum
+
+@see https://vuetifyjs.com/en/components/selects/
+-->
+
 <template>
   <main>
     <v-skeleton-loader
       v-if="$fetchState.pending"
       type="list-item"
       loading
-    ></v-skeleton-loader>
+    />
 
     <v-select
       v-else
@@ -13,65 +19,69 @@
       :items="items"
       item-text="label"
       item-value="value"
-      @change="$emit('update', $event, field)"
       :rules="rules"
       :disabled="readOnly"
-    ></v-select>
+      @change="$emit('update', $event, field)"
+    />
   </main>
 </template>
 
 <script lang="ts">
-  import {defineComponent, ref, useContext, useFetch, Ref} from '@nuxtjs/composition-api'
-  import {EnumChoices} from "~/types/interfaces";
-  import {QUERY_TYPE} from "~/types/enums";
+import { defineComponent, ref, useContext, useFetch, Ref } from '@nuxtjs/composition-api'
+import { EnumChoices } from '~/types/interfaces'
+import { QUERY_TYPE } from '~/types/enums'
 
-  export default defineComponent({
-    props: {
-      enumType: {
-        type: String,
-        required: true
-      },
-      label:{
-        type: String,
-        required: false
-      },
-      field:{
-        type: String,
-        required: false
-      },
-      data: {
-        type: String,
-        required: false
-      },
-      readOnly: {
-        type: Boolean,
-        required: false
-      },
-      rules: {
-        type: Array,
-        required: false
-      }
+export default defineComponent({
+  props: {
+    enumType: {
+      type: String,
+      required: true
+    },
+    label: {
+      type: String,
+      required: false,
+      default: ''
     },
-    setup(props){
-      const label_field = props.label ?? props.field
+    field: {
+      type: String,
+      required: false,
+      default: null
+    },
+    data: {
+      type: String,
+      required: false,
+      default: null
+    },
+    readOnly: {
+      type: Boolean,
+      required: false
+    },
+    rules: {
+      type: Array,
+      required: false,
+      default: () => []
+    }
+  },
+  setup (props) {
+    const labelField = props.label ?? props.field
 
-      const { enumType } = props
-      const {$dataProvider} = useContext()
+    const { enumType } = props
+    const { $dataProvider } = useContext()
 
-      const items: Ref<Array<EnumChoices>> = ref([])
-      useFetch(async () => {
-        items.value = await $dataProvider.invoke({
-          type: QUERY_TYPE.ENUM,
-          enumType: enumType
-        })
+    const items: Ref<Array<EnumChoices>> = ref([])
+    useFetch(async () => {
+      items.value = await $dataProvider.invoke({
+        type: QUERY_TYPE.ENUM,
+        enumType
       })
+    })
 
-      return {
-        items,
-        label_field
-      }
+    return {
+      items,
+      label_field: labelField
     }
-  })
+  }
+})
 </script>
 
 <style scoped>

+ 63 - 52
components/Ui/Input/Text.vue

@@ -1,62 +1,73 @@
+<!--
+Champs de saisie de texte
+
+@see https://vuetifyjs.com/en/components/text-fields/
+-->
+
 <template>
-    <v-text-field
-      autocomplete="off"
-      :value="data"
-      :label="$t(label_field)"
-      @change="$emit('update', $event, field)"
-      :rules="rules"
-      :disabled="readOnly"
-      :type="type"
-      :error="error"
-      :error-messages="errorMessage"
-    >
-    </v-text-field>
+  <v-text-field
+    autocomplete="off"
+    :value="data"
+    :label="$t(label_field)"
+    :rules="rules"
+    :disabled="readOnly"
+    :type="type"
+    :error="error"
+    :error-messages="errorMessage"
+    @change="$emit('update', $event, field)"
+  />
 </template>
 
 <script lang="ts">
-  import {defineComponent} from '@nuxtjs/composition-api'
+import { defineComponent } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    props: {
-      label:{
-        type: String,
-        required: false
-      },
-      field:{
-        type: String,
-        required: false
-      },
-      type:{
-        type: String,
-        required: false
-      },
-      data: {
-        type: [String, Number],
-        required: false
-      },
-      readOnly: {
-        type: Boolean,
-        required: false
-      },
-      rules:{
-        type: Array,
-        required: false
-      },
-      error:{
-        type: Boolean,
-        required: false
-      },
-      errorMessage:{
-        type: String,
-        required: false
-      }
+export default defineComponent({
+  props: {
+    label: {
+      type: String,
+      required: false,
+      default: ''
+    },
+    field: {
+      type: String,
+      required: false,
+      default: null
+    },
+    type: {
+      type: String,
+      required: false,
+      default: null
+    },
+    data: {
+      type: [String, Number],
+      required: false,
+      default: null
     },
-    setup(props){
-      return {
-        label_field : props.label ?? props.field
-      }
+    readOnly: {
+      type: Boolean,
+      required: false
+    },
+    rules: {
+      type: Array,
+      required: false,
+      default: () => []
+    },
+    error: {
+      type: Boolean,
+      required: false
+    },
+    errorMessage: {
+      type: String,
+      required: false,
+      default: null
     }
-  })
+  },
+  setup (props) {
+    return {
+      label_field: props.label ?? props.field
+    }
+  }
+})
 </script>
 
 <style>

+ 72 - 66
components/Ui/Map.vue

@@ -1,95 +1,101 @@
+<!--
+Leaflet map
+
+@see https://leafletjs.com/
+-->
+
 <template>
   <div id="map-wrap">
     <client-only>
-      <l-map :zoom=zoom :center="center">
-        <l-tile-layer :url="layerUrl"></l-tile-layer>
+      <l-map :zoom="zoom" :center="center">
+        <l-tile-layer :url="layerUrl" />
         <l-marker
           :lat-lng="latLong"
           draggable
-          v-on:update:latLng="onMoveMarker"></l-marker>
+          @update:latLng="onMoveMarker"
+        />
       </l-map>
 
       <v-btn class="mr-4 ot_green ot_white--text" @click="updateMap">
-        {{$t('updateMap')}}
+        {{ $t('updateMap') }}
       </v-btn>
-
     </client-only>
   </div>
 </template>
 
 <script lang="ts">
-  import {defineComponent, computed,ref, toRefs, useContext, ComputedRef, Ref} from '@nuxtjs/composition-api'
-  import {QUERY_TYPE, TYPE_ALERT} from "~/types/enums";
-  import {AddressPostal} from "~/models/Core/AddressPostal";
-  import {AnyJson} from "~/types/interfaces";
-  import {alert} from "~/types/interfaces";
-  import {ToRefs} from "@vue/composition-api";
+import {
+  defineComponent, computed, ref, toRefs, useContext, ComputedRef, Ref, ToRefs
+} from '@nuxtjs/composition-api'
+import { QUERY_TYPE, TYPE_ALERT } from '~/types/enums'
+import { AddressPostal } from '~/models/Core/AddressPostal'
+import { AnyJson, alert } from '~/types/interfaces'
 
-  export default defineComponent({
-    props:{
-      address:{
-        type: Object as () => AddressPostal,
-        required: true
-      },
-      zoom:{
-        type: Number,
-        required: true
-      }
+export default defineComponent({
+  props: {
+    address: {
+      type: Object as () => AddressPostal,
+      required: true
     },
-    setup(props, {emit}){
-      const {$dataProvider, store} = useContext()
-      const {address}:ToRefs = toRefs(props)
-      const latitude:Ref<number> = ref(address.value.latitude)
-      const longitude:Ref<number> = ref(address.value.longitude)
+    zoom: {
+      type: Number,
+      required: true
+    }
+  },
+  setup (props, { emit }) {
+    const { $dataProvider, store } = useContext()
+    const { address }: ToRefs = toRefs(props)
+    const latitude: Ref<number> = ref(address.value.latitude)
+    const longitude: Ref<number> = ref(address.value.longitude)
 
-      const center:ComputedRef<Array<number>> = computed(() => [latitude.value,longitude.value])
-      const latLong:ComputedRef<Array<number>> = computed(() => [latitude.value,longitude.value])
-      const layerUrl:string = 'https://{s}.tile.osm.org/{z}/{x}/{y}.png'
+    const center: ComputedRef<Array<number>> = computed(() => [latitude.value, longitude.value])
+    const latLong: ComputedRef<Array<number>> = computed(() => [latitude.value, longitude.value])
+    const layerUrl: string = 'https://{s}.tile.osm.org/{z}/{x}/{y}.png'
 
-      const updateMap = async () => {
-        let response = await $dataProvider.invoke({
-          type: QUERY_TYPE.DEFAULT,
-          url: `gps-coordinate-searching?street=${address.value.streetAddress} ${address.value.streetAddressSecond} ${address.value.streetAddressThird}&cp=${address.value.postalCode}&city=${address.value.addressCity}`
-        })
-        if(response.length > 0){
-          latitude.value = response[0].latitude
-          longitude.value = response[0].longitude
+    const updateMap = async () => {
+      const response = await $dataProvider.invoke({
+        type: QUERY_TYPE.DEFAULT,
+        url: `gps-coordinate-searching?street=${address.value.streetAddress} ${address.value.streetAddressSecond} ${address.value.streetAddressThird}&cp=${address.value.postalCode}&city=${address.value.addressCity}`
+      })
+      if (response.length > 0) {
+        latitude.value = response[0].latitude
+        longitude.value = response[0].longitude
 
-          address.value.latitude = response[0].latitude
-          address.value.longitude = response[0].longitude
-          emit('updateAddress', address.value)
-        }else{
-          const alert:alert = {
-            type: TYPE_ALERT.ALERT,
-            message: 'no_coordinate_corresponding'
-          }
-          store.commit('page/setAlert', alert)
+        address.value.latitude = response[0].latitude
+        address.value.longitude = response[0].longitude
+        emit('updateAddress', address.value)
+      } else {
+        const alert: alert = {
+          type: TYPE_ALERT.ALERT,
+          message: 'no_coordinate_corresponding'
         }
+        store.commit('page/setAlert', alert)
       }
+    }
 
-      const onMoveMarker = async (event:AnyJson) => {
-        let response = await $dataProvider.invoke({
-          type: QUERY_TYPE.DEFAULT,
-          url: `gps-coordinate-reverse/${event.lat}/${event.lng}`
-        })
-        address.value.streetAddress = response.streetAddress
-        address.value.streetAddressSecond = response.streetAddressSecond
-        address.value.streetAddressThird = response.streetAddressThird
-        address.value.postalCode = response.cp
-        address.value.addressCity = response.city
+    const onMoveMarker = async (event: AnyJson) => {
+      const response = await $dataProvider.invoke({
+        type: QUERY_TYPE.DEFAULT,
+        url: `gps-coordinate-reverse/${event.lat}/${event.lng}`
+      })
+      address.value.streetAddress = response.streetAddress
+      address.value.streetAddressSecond = response.streetAddressSecond
+      address.value.streetAddressThird = response.streetAddressThird
+      address.value.postalCode = response.cp
+      address.value.addressCity = response.city
 
-        emit('updateAddress', address.value)
-      }
+      emit('updateAddress', address.value)
+    }
 
-      return {
-        updateMap,
-        center,
-        latLong,
-        layerUrl,
-        onMoveMarker
-      }
+    return {
+      updateMap,
+      center,
+      latLong,
+      layerUrl,
+      onMoveMarker
     }
-  })
+  }
+})
 
 </script>
 

+ 53 - 50
components/Ui/SubList.vue

@@ -1,61 +1,64 @@
+<!-- ? -->
+
 <template>
-    <main>
-      <v-skeleton-loader
-        type="text"
-        v-if="$fetchState.pending"
-      ></v-skeleton-loader>
-      <div v-else>
-        <slot name="list.item" v-bind="{items}"></slot>
-      </div>
-      <slot>
-      </slot>
-    </main>
+  <main>
+    <v-skeleton-loader
+      v-if="$fetchState.pending"
+      type="text"
+    />
+    <div v-else>
+      <slot name="list.item" v-bind="{items}" />
+    </div>
+    <slot />
+  </main>
 </template>
 
 <script lang="ts">
-  import {defineComponent, ref, computed, useContext, useFetch, toRefs, onUnmounted, ToRefs, ComputedRef} from '@nuxtjs/composition-api'
-  import {Query} from "@vuex-orm/core";
-  import {queryHelper} from "~/services/store/query";
-  import {QUERY_TYPE} from "~/types/enums";
-  import {Collection} from "@vuex-orm/core/dist/src/data/Data";
+import {
+  defineComponent, computed, useContext, useFetch, toRefs, ToRefs, ComputedRef
+} from '@nuxtjs/composition-api'
+import { Query } from '@vuex-orm/core'
+import { Collection } from '@vuex-orm/core/dist/src/data/Data'
+import { queryHelper } from '~/services/store/query'
+import { QUERY_TYPE } from '~/types/enums'
 
-  export default defineComponent({
-    props: {
-      rootModel:{
-        type: Function,
-        required: true
-      },
-      rootId:{
-        type: Number,
-        required: true
-      },
-      model:{
-        type: Function,
-        required: true
-      },
-      query:{
-        type: Object as () => Query,
-        required: true
-      }
+export default defineComponent({
+  props: {
+    rootModel: {
+      type: Function,
+      required: true
+    },
+    rootId: {
+      type: Number,
+      required: true
     },
-    setup(props) {
-      const {rootModel, rootId, model, query}:ToRefs = toRefs(props);
-      const {$dataProvider} = useContext()
-      useFetch(async ()=>{
-        await $dataProvider.invoke({
-          type: QUERY_TYPE.MODEL,
-          model: model.value,
-          root_model: rootModel.value,
-          root_id: rootId.value
-        })
+    model: {
+      type: Function,
+      required: true
+    },
+    query: {
+      type: Object as () => Query,
+      required: true
+    }
+  },
+  setup (props) {
+    const { rootModel, rootId, model, query }: ToRefs = toRefs(props)
+    const { $dataProvider } = useContext()
+    useFetch(async () => {
+      await $dataProvider.invoke({
+        type: QUERY_TYPE.MODEL,
+        model: model.value,
+        rootModel: rootModel.value,
+        rootId: rootId.value
       })
+    })
 
-      const items:ComputedRef<Collection>  = computed(() => queryHelper.getCollection(query.value))
-      // onUnmounted( useRepositoryHelper.cleanRepository(repository.value) )
+    const items: ComputedRef<Collection> = computed(() => queryHelper.getCollection(query.value))
+    // onUnmounted( useRepositoryHelper.cleanRepository(repository.value) )
 
-      return {
-        items
-      }
+    return {
+      items
     }
-  })
+  }
+})
 </script>

+ 54 - 46
components/Ui/Xeditable/Text.vue

@@ -1,57 +1,65 @@
+<!--
+?
+-->
+
 <template>
-    <main>
-      <div v-if="edit" class="d-flex align-baseline x-editable-input mt-n1">
-        <UiInputText
-          class="mt-0 pt-0 mt-n1"
-            :type="type"
-            :data="inputValue"
-            v-on:update="inputValue=$event"
-        ></UiInputText>
-        <v-icon aria-hidden="false" class="valid icons ot_green--text" small @click="update">fas fa-check</v-icon>
-        <v-icon aria-hidden="false" class="cancel icons ot_grey--text" small @click="close">fas fa-times</v-icon>
-      </div>
-      <div v-else @click="edit=true" class="edit-link">
-        <slot name="xeditable.read" v-bind="{inputValue}"></slot>
-      </div>
-    </main>
+  <main>
+    <div v-if="edit" class="d-flex align-baseline x-editable-input mt-n1">
+      <UiInputText
+        class="mt-0 pt-0 mt-n1"
+        :type="type"
+        :data="inputValue"
+        @update="inputValue=$event"
+      />
+      <v-icon aria-hidden="false" class="valid icons ot_green--text" small @click="update">
+        fas fa-check
+      </v-icon>
+      <v-icon aria-hidden="false" class="cancel icons ot_grey--text" small @click="close">
+        fas fa-times
+      </v-icon>
+    </div>
+    <div v-else class="edit-link" @click="edit=true">
+      <slot name="xeditable.read" v-bind="{inputValue}" />
+    </div>
+  </main>
 </template>
 
 <script lang="ts">
-  import {defineComponent, ref, Ref} from '@nuxtjs/composition-api'
+import { defineComponent, ref, Ref } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    props: {
-      type:{
-        type: String,
-        required: false
-      },
-      data: {
-        type: [String, Number],
-        required: false,
-        default:null
-      }
+export default defineComponent({
+  props: {
+    type: {
+      type: String,
+      required: false,
+      default: null
     },
-    setup(props, {emit}){
-      const edit:Ref<boolean> = ref(false)
-      const inputValue:Ref<string|number|null> = ref(props.data)
+    data: {
+      type: [String, Number],
+      required: false,
+      default: null
+    }
+  },
+  setup (props, { emit }) {
+    const edit: Ref<boolean> = ref(false)
+    const inputValue: Ref<string|number|null> = ref(props.data)
 
-      const update = () =>{
-        edit.value = false
-        if(inputValue.value !== props.data)
-          emit('update', inputValue.value)
-      }
-      const close = () =>{
-        edit.value = false
-        inputValue.value = props.data
-      }
-      return {
-        inputValue,
-        edit,
-        update,
-        close
-      }
+    const update = () => {
+      edit.value = false
+      if (inputValue.value !== props.data) { emit('update', inputValue.value) }
+    }
+    const close = () => {
+      edit.value = false
+      inputValue.value = props.data
     }
-  })
+    return {
+      inputValue,
+      edit,
+      update,
+      close
+    }
+  }
+})
 </script>
 
 <style lang="scss">

+ 3 - 9
config/nuxtConfig/build.js

@@ -1,10 +1,10 @@
 import webpack from 'webpack'
 
 export default {
-  // Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
+  // Enable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
   ssr: true,
 
-  // // Auto import components (https://go.nuxtjs.dev/config-components)
+  // Auto import components (https://go.nuxtjs.dev/config-components)
   components: true,
 
   loading: '~/components/Layout/Loading.vue',
@@ -23,13 +23,7 @@ export default {
     ]
   },
 
-  //Port and local host
-  server: {
-    port: 3002,
-    host: '0.0.0.0', // default: localhost,
-  },
-
-  //Poll for hot reloading with docker
+  // Poll for hot reloading with docker
   watchers: {
     webpack: {
       aggregateTimeout: 300,

+ 2 - 2
config/nuxtConfig/env.js

@@ -6,7 +6,7 @@ export default {
     artist_premium_product: 'artist-premium',
     manager_product: 'manager',
     cmf_network: 'CMF',
-    ffec_network: 'FFEC',
+    ffec_network: 'FFEC'
   },
   publicRuntimeConfig: {
     http: {
@@ -22,7 +22,7 @@ export default {
   },
   privateRuntimeConfig: {
     http: {
-      https:true,
+      https: true,
       baseURL: process.env.NODE_ENV !== 'production' ? 'http://nginx_new' : 'https://local.api.opentalent.fr'
     },
     axios: {

+ 7 - 6
config/nuxtConfig/head.js

@@ -4,19 +4,20 @@ export default {
     titleTemplate: '%s - admin',
     title: 'admin',
     meta: [
-      {charset: 'utf-8'},
-      {name: 'viewport', content: 'width=device-width, initial-scale=1'},
-      {hid: 'description', name: 'description', content: ''}
+      { charset: 'utf-8' },
+      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
+      { hid: 'description', name: 'description', content: '' }
     ],
     link: [
-      {rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'}
+      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
     ]
   },
 
   // Global CSS (https://go.nuxtjs.dev/config-css)
+  // (https://nuxtjs.org/docs/2.x/features/configuration#the-css-property)
   css: [
-    '@/assets/css/import.scss',
-    '@/assets/css/global.scss',
+    '~/assets/css/import.scss',
+    '~/assets/css/global.scss',
     '@fortawesome/fontawesome-free/css/all.css'
   ]
 }

+ 4 - 2
config/nuxtConfig/modules.js

@@ -6,13 +6,15 @@ export default {
     // // https://go.nuxtjs.dev/vuetify
     '@nuxtjs/vuetify',
     '@nuxtjs/moment',
-    '@nuxtjs/composition-api/module'
+    '@nuxtjs/composition-api/module',
+    '@nuxt/image'
   ],
 
   // Modules (https://go.nuxtjs.dev/config-modules)
   modules: [
     '@nuxtjs/axios',
     'nuxt-leaflet',
-    'nuxt-i18n'
+    '@nuxtjs/i18n',
+    '@nuxt/image'
   ]
 }

+ 1 - 1
config/nuxtConfig/plugins.js

@@ -8,6 +8,6 @@ export default {
     '~/plugins/Data/axios',
     '~/plugins/Data/dataPersister',
     '~/plugins/Data/dataProvider',
-    '~/plugins/Data/dataDeleter',
+    '~/plugins/Data/dataDeleter'
   ]
 }

+ 1 - 1
config/nuxtConfig/vuetify.js

@@ -4,7 +4,7 @@ export default {
   // Vuetify module configuration (https://go.nuxtjs.dev/config-vuetify)
   vuetify: {
     icons: {
-      iconfont: 'fa' || 'mdi',
+      iconfont: 'fa' || 'mdi'
     },
     customVariables: ['~/assets/css/variables.scss'],
     treeShake: true,

+ 1 - 1
jest.config.js

@@ -25,5 +25,5 @@ module.exports = {
     '<rootDir>/use/**/*.ts',
     '<rootDir>/pages/**/*.vue'
   ],
-  setupFiles: ["<rootDir>/tests/unit/index.ts"],
+  setupFiles: ['<rootDir>/tests/unit/index.ts']
 }

+ 56 - 56
lang/enum/fr-FR.js

@@ -3,63 +3,63 @@ export default (context, locale) => {
     LOCAL_AUTHORITY: 'Collectivité territoriale (Mairie, SIVOM, SIVU, EPIC, …)',
     ASSOCIATION_LAW_1901: 'Association loi 1901 ou assimilée (Droit local, ...)',
     COMMERCIAL_SOCIETY: 'Entreprise commerciale (SARL, SAS, EURL, Autoentrepreneur, …)',
-    ARTISTIC_EDUCATION_ONLY: "Enseignement artistique seul",
-    ARTISTIC_PRACTICE_EDUCATION: "Pratique et enseignement artistique",
-    ARTISTIC_PRACTICE_ONLY: "Pratique artistique seule",
-    DELEGATION: "Délégation",
-    DEPARTEMENTAL_FEDERATION: "Fédération départementale",
-    GROUPMENT: "Groupement",
-    LOCAL_FEDERATION: "Fédération locale",
-    MUSIC_OPENTALENT: "Opentalent",
-    NATIONAL_FEDERATION: "Fédération nationale",
-    REGIONAL_FEDERATION: "Fédération régionale",
+    ARTISTIC_EDUCATION_ONLY: 'Enseignement artistique seul',
+    ARTISTIC_PRACTICE_EDUCATION: 'Pratique et enseignement artistique',
+    ARTISTIC_PRACTICE_ONLY: 'Pratique artistique seule',
+    DELEGATION: 'Délégation',
+    DEPARTEMENTAL_FEDERATION: 'Fédération départementale',
+    GROUPMENT: 'Groupement',
+    LOCAL_FEDERATION: 'Fédération locale',
+    MUSIC_OPENTALENT: 'Opentalent',
+    NATIONAL_FEDERATION: 'Fédération nationale',
+    REGIONAL_FEDERATION: 'Fédération régionale',
     CESMD: "CESMD Centre d'études supérieures de musique et de danse",
-    CNSMD: "CNSMD Conservatoire national supérieur de musique",
-    CRC: "CRC Conservatoire à rayonnement communal",
-    CRD: "CRD Conservatoire à rayonnement départemental",
-    CRI: "CRI Conservatoire à rayonnement intercommunal",
-    CRR: "CRR Conservatoire à rayonnement régional",
+    CNSMD: 'CNSMD Conservatoire national supérieur de musique',
+    CRC: 'CRC Conservatoire à rayonnement communal',
+    CRD: 'CRD Conservatoire à rayonnement départemental',
+    CRI: 'CRI Conservatoire à rayonnement intercommunal',
+    CRR: 'CRR Conservatoire à rayonnement régional',
     EENC: "EENC Établissement d'enseignement artistique non classé",
-    EMP: "EMP Ecole de musique privée",
-    MULTIPLE: "Multiple",
-    UNIQUE: "Unique",
-    MAIN_BUILDING: "Etablissement principal",
-    SECONDARY_SCHOOL: "Etablissement secondaire",
-    ACTALIANS: "Actalians",
-    AFDAS: "Afdas",
-    AGEFOS: "Agefos",
-    AGEFOS_PME: "Agefos pme",
-    ANFA: "Anfa",
-    ANFH: "Anfh",
-    APCMA: "Apcma",
-    CNFPT: "Cnfpt",
-    CONSTRUCTYS: "Constructys",
-    FAF_TT: "Faf-tt",
-    FAFIEC: "Fafiec",
-    FAFIH: "Fafih",
-    FAFSEA: "Fafsea",
-    FIF_PL: "Fif pl",
-    FONGECIF: "Fongecif",
-    FORCO: "Forco",
-    INTERGROS: "Intergros",
-    OPCA3_PLUS: "Opca3+",
-    OPCAIM: "Opcaim",
-    OPCALIA: "Opcalia",
-    OPCALIM: "Opcalim",
-    OPCA_BAIA: "Opca baia",
-    OPCA_DEFI: "Opca defi",
-    OPCA_TRANSPORTS: "Opca transports",
-    UNIFAF: "Unifaf",
-    UNIFORMATION: "Uniformation",
-    VIVEA: "Vivea",
-    ADDRESS_PRACTICE: "Adresse de pratique",
-    ADDRESS_HEAD_OFFICE: "Adresse du siège social",
-    ADDRESS_CONTACT: "Adresse de contact",
-    ADDRESS_BILL: "Adresse de facturation",
-    ADDRESS_OTHER: "Autre adresse",
-    PRINCIPAL: "Contact principal",
-    BILL: "Contact de facturation",
-    OTHER: "Autre contact",
-    CONTACT: "Contact",
+    EMP: 'EMP Ecole de musique privée',
+    MULTIPLE: 'Multiple',
+    UNIQUE: 'Unique',
+    MAIN_BUILDING: 'Etablissement principal',
+    SECONDARY_SCHOOL: 'Etablissement secondaire',
+    ACTALIANS: 'Actalians',
+    AFDAS: 'Afdas',
+    AGEFOS: 'Agefos',
+    AGEFOS_PME: 'Agefos pme',
+    ANFA: 'Anfa',
+    ANFH: 'Anfh',
+    APCMA: 'Apcma',
+    CNFPT: 'Cnfpt',
+    CONSTRUCTYS: 'Constructys',
+    FAF_TT: 'Faf-tt',
+    FAFIEC: 'Fafiec',
+    FAFIH: 'Fafih',
+    FAFSEA: 'Fafsea',
+    FIF_PL: 'Fif pl',
+    FONGECIF: 'Fongecif',
+    FORCO: 'Forco',
+    INTERGROS: 'Intergros',
+    OPCA3_PLUS: 'Opca3+',
+    OPCAIM: 'Opcaim',
+    OPCALIA: 'Opcalia',
+    OPCALIM: 'Opcalim',
+    OPCA_BAIA: 'Opca baia',
+    OPCA_DEFI: 'Opca defi',
+    OPCA_TRANSPORTS: 'Opca transports',
+    UNIFAF: 'Unifaf',
+    UNIFORMATION: 'Uniformation',
+    VIVEA: 'Vivea',
+    ADDRESS_PRACTICE: 'Adresse de pratique',
+    ADDRESS_HEAD_OFFICE: 'Adresse du siège social',
+    ADDRESS_CONTACT: 'Adresse de contact',
+    ADDRESS_BILL: 'Adresse de facturation',
+    ADDRESS_OTHER: 'Autre adresse',
+    PRINCIPAL: 'Contact principal',
+    BILL: 'Contact de facturation',
+    OTHER: 'Autre contact',
+    CONTACT: 'Contact'
   })
 }

+ 57 - 57
lang/field/fr-FR.js

@@ -5,67 +5,67 @@ export default (context, locale) => {
     salary: 'Salariés',
     network: 'Informations réseau',
     communication: 'Communication',
-    legalStatus: "Statut juridique",
-    siretNumber: "N° SIRET",
-    apeNumber: "Code APE",
-    waldecNumber: "RNA (ancien Waldec)",
-    identifierCmf: "Matricule CMF",
-    identifierFfec: "Matricule FFEC",
-    ffecApproval: "N° agrément FFEC",
-    description: "Description",
-    typeOfPractices: "Type de pratiques",
-    otherPractice: "Autres ensembles (préciser)",
-    principalType: "Type principal",
-    contact_point: "Point de contact",
-    name: "Nom",
-    acronym: "Sigle",
-    creationDate: "Date de création",
-    prefectureName: "Préfecture ou sous-préfecture",
-    prefectureNumber: "Numéro de déclaration",
-    declarationDate: "Date de déclaration",
-    tvaNumber: "TVA Intracommunautaire",
+    legalStatus: 'Statut juridique',
+    siretNumber: 'N° SIRET',
+    apeNumber: 'Code APE',
+    waldecNumber: 'RNA (ancien Waldec)',
+    identifierCmf: 'Matricule CMF',
+    identifierFfec: 'Matricule FFEC',
+    ffecApproval: 'N° agrément FFEC',
+    description: 'Description',
+    typeOfPractices: 'Type de pratiques',
+    otherPractice: 'Autres ensembles (préciser)',
+    principalType: 'Type principal',
+    contact_point: 'Point de contact',
+    name: 'Nom',
+    acronym: 'Sigle',
+    creationDate: 'Date de création',
+    prefectureName: 'Préfecture ou sous-préfecture',
+    prefectureNumber: 'Numéro de déclaration',
+    declarationDate: 'Date de déclaration',
+    tvaNumber: 'TVA Intracommunautaire',
     schoolCategory: "Catégorie d'école",
     typeEstablishment: "Type d'établissement",
-    typeEstablishmentDetail: "Détails du type",
-    youngApproval: "Jeunesse-éducation populaire",
-    trainingApproval: "Organisme de formation",
-    otherApproval: "Si autre, lesquels",
-    collectiveAgreement: "Nom de la convention collective",
+    typeEstablishmentDetail: 'Détails du type',
+    youngApproval: 'Jeunesse-éducation populaire',
+    trainingApproval: 'Organisme de formation',
+    otherApproval: 'Si autre, lesquels',
+    collectiveAgreement: 'Nom de la convention collective',
     opca: "Nom de l'OPCA",
-    icomNumber: "N° ICOM",
-    urssafNumber: "N° URSSAF",
-    email: "E-mail",
-    emailInvalid: "E-mail invalide",
-    telphone: "Téléphone",
-    telphoneInvalid: "Téléphone invalide",
-    mobilPhone: "Portable",
-    mobilPhoneInvalid: "Portable invalide",
-    actions: "Actions",
-    twitter: "Lien Twitter",
-    facebook: "Lien Facebook",
-    instagram: "Lien Instagram",
-    image: "Image",
+    icomNumber: 'N° ICOM',
+    urssafNumber: 'N° URSSAF',
+    email: 'E-mail',
+    emailInvalid: 'E-mail invalide',
+    telphone: 'Téléphone',
+    telphoneInvalid: 'Téléphone invalide',
+    mobilPhone: 'Portable',
+    mobilPhoneInvalid: 'Portable invalide',
+    actions: 'Actions',
+    twitter: 'Lien Twitter',
+    facebook: 'Lien Facebook',
+    instagram: 'Lien Instagram',
+    image: 'Image',
     portailVisibility: "Répertorier la structure dans l'annuaire du portail Opentalent",
-    pedagogicBudget: "Budget pédagogique",
-    budget: "Montant du dernier budget réalisé",
+    pedagogicBudget: 'Budget pédagogique',
+    budget: 'Montant du dernier budget réalisé',
     isPedagogicIsPrincipalActivity: "L'activité principale de la stucture est « la pédagogie des arts du cirque »",
-    bank_account: "IBAN",
-    bankName: "Nom de la banque",
-    bic: "Code BIC",
-    iban: "IBAN",
-    holder: "Titulaire du compte",
-    debitAddress: "Domiciliation",
-    principal: "Principal",
-    address_postal: "Adresses postales",
-    address: "Adresse",
-    address_postal_type: "Nature",
-    addressOwner: "Chez",
-    streetAddress: "Adresses",
-    streetAddressSecond: "Adresses suite",
-    streetAddressThird: "Adresses suite 2",
-    postalCode: "Code postal",
-    addressCity: "Ville",
-    country: "Pays",
-    addresstype: "Nature",
+    bank_account: 'IBAN',
+    bankName: 'Nom de la banque',
+    bic: 'Code BIC',
+    iban: 'IBAN',
+    holder: 'Titulaire du compte',
+    debitAddress: 'Domiciliation',
+    principal: 'Principal',
+    address_postal: 'Adresses postales',
+    address: 'Adresse',
+    address_postal_type: 'Nature',
+    addressOwner: 'Chez',
+    streetAddress: 'Adresses',
+    streetAddressSecond: 'Adresses suite',
+    streetAddressThird: 'Adresses suite 2',
+    postalCode: 'Code postal',
+    addressCity: 'Ville',
+    country: 'Pays',
+    addresstype: 'Nature'
   })
 }

+ 2 - 2
lang/form/fr-FR.js

@@ -13,7 +13,7 @@ export default (context, locale) => {
     attention: 'Attention',
     updateMap: 'Mise à jour de la carte',
     start_your_research: 'Commencer à écrire pour rechercher...',
-    no_coordinate_corresponding: "Aucune coordonnées GPS ne correspondent à votre adresse",
-    quit_without_saving_warning: 'Vous souhaitez quitter ce formulaire sans avoir enregistré',
+    no_coordinate_corresponding: 'Aucune coordonnées GPS ne correspondent à votre adresse',
+    quit_without_saving_warning: 'Vous souhaitez quitter ce formulaire sans avoir enregistré'
   })
 }

+ 1 - 2
lang/fr-FR.js

@@ -7,7 +7,6 @@ import breadcrumbs from '@/lang/breadcrumbs/fr-FR'
 import menuKey from '@/lang/menuKey/fr-FR'
 
 export default (context, locale) => {
-
   return {
     ...layout(context, locale),
     ...enums(context, locale),
@@ -15,6 +14,6 @@ export default (context, locale) => {
     ...rulesAndErrors(context, locale),
     ...form(context, locale),
     ...breadcrumbs(context, locale),
-    ...menuKey(context, locale),
+    ...menuKey(context, locale)
   }
 }

+ 12 - 6
lang/layout/fr-FR.js

@@ -41,12 +41,12 @@ export default (context, locale) => {
     parameters_cotisation: 'Paramètrer l\'appel de cotisation',
     send_cotisation: 'Appel des cotisations',
     state_cotisation: 'Suivi des cotisations',
-    pay_cotisation: 'Saisie des réglements',
+    pay_cotisation: 'Saisie des règlements',
     check_cotisation: 'Remise de chèques',
-    ledger_cotisation: 'Journal des réglements',
+    ledger_cotisation: 'Journal des règlements',
     magazine_cotisation: 'Bulletin',
-    ventilated_cotisation: 'Cotisations ventilées par sous total',
-    pay_erase_cotisation: 'Suppression de réglements',
+    ventilated_cotisation: 'Cotisations ventilées par sous-total',
+    pay_erase_cotisation: 'Suppression de règlements',
     resume_cotisation: 'Etat des transmissions',
     history_cotisation: 'Historique des cotisations',
     call_cotisation: 'Règlements à la fédération',
@@ -97,10 +97,16 @@ export default (context, locale) => {
     present: 'Présent',
     future: 'Future',
     notification: 'Notifications',
-    history_help: 'Personnaliser la pédiode d\'affichage',
+    history_help: 'Personnaliser la période d\'affichage',
     period_choose: 'Période à afficher',
     date_choose: 'Choix de la période',
     my_list: 'Mes listes',
-    searchList: 'Rechercher parmis mes listes personnalisées',
+    searchList: 'Rechercher parmi mes listes personnalisées',
+    my_subscription: 'Mon abonnement',
+    informations: 'Informations',
+    more_features: 'Plus de fonctionnalités',
+    client_id: 'Numéro de client',
+    version: 'Version',
+    paying_structure: 'Établissement payeur'
   })
 }

+ 2 - 2
lang/menuKey/fr-FR.js

@@ -1,7 +1,7 @@
 export default (context, locale) => {
   return ({
     attendanceListMenuKey: 'Absences',
-    billAndBillCreditListMenuKey: "Facturation",
-    personRepertoryListMenuKey: "Répertoire",
+    billAndBillCreditListMenuKey: 'Facturation',
+    personRepertoryListMenuKey: 'Répertoire'
   })
 }

+ 2 - 2
lang/rulesAndErrors/fr-FR.js

@@ -1,7 +1,7 @@
 export default (context, locale) => {
   return ({
     required: 'Ce champs est obligatoire',
-    name_length_rule: "La taille du nom doit être de moins de 128 caractères",
-    siret_error: "N° de siret non valide",
+    name_length_rule: 'La taille du nom doit être de moins de 128 caractères',
+    siret_error: 'N° de siret non valide'
   })
 }

+ 37 - 36
layouts/default.vue

@@ -1,62 +1,63 @@
 <template>
   <main>
-    <client-only placeholder=" ">
-    </client-only>
+    <!-- The client only is used to show the loading picture (@see https://nuxtjs.org/docs/2.x/features/nuxt-components#the-client-only-component) -->
+    <client-only placeholder="Loading..." />
 
     <v-app dark>
+      <LayoutMenu v-if="displayedMenu" :menu="menu" :mini-variant="properties.miniVariant" />
 
-      <LayoutMenu  v-if="displayedMenu" :menu="menu" :miniVariant="properties.miniVariant"></LayoutMenu>
-
-      <LayoutHeader v-on:handle-open-menu-click="handleOpenMenu"></LayoutHeader>
+      <LayoutHeader @handle-open-menu-click="handleOpenMenu" />
 
       <v-main class="ot_content_color">
-        <LayoutSubheader v-if="displayedSubHeader"></LayoutSubheader>
-        <nuxt/>
+        <LayoutSubheader v-if="displayedSubHeader" />
+
+        <!-- Page will be rendered here-->
+        <nuxt />
       </v-main>
 
-      <lazy-LayoutAlertContainer></lazy-LayoutAlertContainer>
+      <lazy-LayoutAlertContainer />
     </v-app>
   </main>
 </template>
 
 <script lang="ts">
-  import {computed, ComputedRef, defineComponent, reactive, useContext} from '@nuxtjs/composition-api'
-  import {$useMenu} from '@/use/layout/menu'
-
-  export default defineComponent({
-    name: 'defaultLayout',
-    setup() {
-      const {store} = useContext()
-      const menu = $useMenu.setUpContext().useLateralMenuConstruct()
+import { computed, ComputedRef, defineComponent, reactive, useContext } from '@nuxtjs/composition-api'
+import { $useMenu } from '@/use/layout/menu'
 
-      const properties = reactive({
-        clipped: false,
-        miniVariant: false
-      })
-      const displayedMenu:ComputedRef<boolean> = computed(()=>store.state.profile.access.hasLateralMenu)
+export default defineComponent({
+  name: 'DefaultLayout',
 
-      const displayedSubHeader:ComputedRef<boolean> = computed(()=>store.state.profile.access.hasLateralMenu || store.state.profile.access.isTeacher)
+  middleware: ['auth'],
+  setup () {
+    const { store } = useContext()
+    const menu = $useMenu.setupContext().useLateralMenuConstruct()
 
-      const handleOpenMenu = (miniVariant:boolean) => {
-        properties.miniVariant = miniVariant
-      }
+    const properties = reactive({
+      clipped: false,
+      miniVariant: false
+    })
 
-      return {
-        properties,
-        menu,
-        displayedMenu,
-        displayedSubHeader,
-        handleOpenMenu
-      }
-    },
+    const displayedMenu: ComputedRef<boolean> = computed(() => store.state.profile.access.hasLateralMenu)
+    const displayedSubHeader: ComputedRef<boolean> = computed(
+      () => store.state.profile.access.hasLateralMenu || store.state.profile.access.isTeacher
+    )
 
+    const handleOpenMenu = (miniVariant: boolean) => {
+      properties.miniVariant = miniVariant
+    }
 
-    middleware: ['auth']
-  })
+    return {
+      properties,
+      menu,
+      displayedMenu,
+      displayedSubHeader,
+      handleOpenMenu
+    }
+  }
+})
 </script>
 
 <style scoped>
-
   .client-only-placeholder {
     height: 100%;
     width: 100%;

+ 19 - 19
layouts/error.vue

@@ -13,28 +13,28 @@
 </template>
 
 <script>
-  export default {
-    layout: 'empty',
-    props: {
-      error: {
-        type: Object,
-        default: null
-      }
-    },
-    data () {
-      return {
-        pageNotFound: '404 Not Found',
-        otherError: 'An error occurred'
-      }
-    },
-    head () {
-      const title =
+export default {
+  layout: 'empty',
+  props: {
+    error: {
+      type: Object,
+      default: null
+    }
+  },
+  data () {
+    return {
+      pageNotFound: '404 Not Found',
+      otherError: 'An error occurred'
+    }
+  },
+  head () {
+    const title =
         this.error.statusCode === 404 ? this.pageNotFound : this.otherError
-      return {
-        title
-      }
+    return {
+      title
     }
   }
+}
 </script>
 
 <style scoped>

+ 5 - 5
layouts/login.vue

@@ -9,12 +9,12 @@
 </template>
 
 <script>
-  export default {
-    name: 'loginLayout',
-    data () {
-      return {
+export default {
+  name: 'LoginLayout',
+  data () {
+    return {
 
-      }
     }
   }
+}
 </script>

+ 1 - 1
middleware/auth.ts

@@ -1,6 +1,6 @@
 import { Middleware } from '@nuxt/types'
 
-const auth: Middleware = async ({ store, redirect }) => {
+const auth: Middleware = ({ store, redirect }) => {
   // Si l'utilisateur n'est pas connecté on le redirige vers la page login
   if (!store.state.profile.access) {
     return redirect('/login')

+ 4 - 4
models/Access/MyProfile.ts

@@ -1,13 +1,13 @@
-import {Attr, Num, Model} from '@vuex-orm/core'
-import {Historical} from "~/types/interfaces";
+import { Attr, Num, Model } from '@vuex-orm/core'
+import { Historical } from '~/types/interfaces'
 
-export class MyProfile extends Model{
+export class MyProfile extends Model {
   static entity = 'accesses'
 
   @Attr(null)
   id!: number | null
 
-  @Num(0, {nullable: true})
+  @Num(0, { nullable: true })
   activityYear!: number
 
   @Attr({})

+ 2 - 2
models/Access/PersonalizedList.ts

@@ -1,6 +1,6 @@
-import {Attr, Model, Str} from '@vuex-orm/core'
+import { Attr, Model, Str } from '@vuex-orm/core'
 
-export class PersonalizedList extends Model{
+export class PersonalizedList extends Model {
   static entity = 'personalized_lists'
 
   @Attr(null)

+ 11 - 11
models/Core/AddressPostal.ts

@@ -1,7 +1,7 @@
-import {Attr, Str, HasOne, Num, Model} from '@vuex-orm/core'
-import {Country} from "~/models/Core/Country";
+import { Attr, Str, HasOne, Num, Model } from '@vuex-orm/core'
+import { Country } from '~/models/Core/Country'
 
-export class AddressPostal extends Model{
+export class AddressPostal extends Model {
   static entity = 'address_postals'
 
   @Attr(null)
@@ -10,27 +10,27 @@ export class AddressPostal extends Model{
   @HasOne(() => Country, 'id')
   addressCountry!: Country | null
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   addressCity!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   addressOwner!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   postalCode!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   streetAddress!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   streetAddressSecond!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   streetAddressThird!: string
 
-  @Num(0, {nullable: true})
+  @Num(0, { nullable: true })
   latitude!: number
 
-  @Num(0, {nullable: true})
+  @Num(0, { nullable: true })
   longitude!: number
 }

+ 8 - 8
models/Core/BankAccount.ts

@@ -1,26 +1,26 @@
-import {Attr, Str, Bool, Model} from '@vuex-orm/core'
+import { Attr, Str, Bool, Model } from '@vuex-orm/core'
 
-export class BankAccount extends Model{
+export class BankAccount extends Model {
   static entity = 'bank_accounts'
 
   @Attr(null)
   id!: number | null
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   bankName!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   bic!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   iban!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   debitAddress!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   holder!: string
 
-  @Bool(false, {nullable: false})
+  @Bool(false, { nullable: false })
   principal!: boolean
 }

+ 11 - 11
models/Core/ContactPoint.ts

@@ -1,35 +1,35 @@
-import {Attr, Str, Model} from '@vuex-orm/core'
+import { Attr, Str, Model } from '@vuex-orm/core'
 
-export class ContactPoint extends Model{
+export class ContactPoint extends Model {
   static entity = 'contact_points'
 
   @Attr(null)
   id!: number | null
 
-  @Str('PRINCIPAL', {nullable: false})
+  @Str('PRINCIPAL', { nullable: false })
   contactType!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   email!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   emailInvalid!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   telphone!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   telphoneInvalid!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   mobilPhone!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   mobilPhoneInvalid!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   faxNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   faxNumberInvalid!: string
 }

+ 2 - 2
models/Core/Country.ts

@@ -1,6 +1,6 @@
-import {Attr, Str, Model} from '@vuex-orm/core'
+import { Attr, Str, Model } from '@vuex-orm/core'
 
-export class Country extends Model{
+export class Country extends Model {
   static entity = 'countries'
 
   @Attr(null)

+ 37 - 37
models/Organization/Organization.ts

@@ -1,6 +1,6 @@
-import {Attr, Str, Bool, Num, Model} from '@vuex-orm/core'
+import { Attr, Str, Bool, Num, Model } from '@vuex-orm/core'
 
-export class Organization extends Model{
+export class Organization extends Model {
   static entity = 'organizations'
 
   @Attr(null)
@@ -9,108 +9,108 @@ export class Organization extends Model{
   @Attr({})
   originalState!: object | null
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   name!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   acronym!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   siretNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   apeNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   waldecNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   identifier!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   ffecApproval!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   description!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   otherPractice!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   legalStatus!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   principalType!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   youngApproval!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   trainingApproval!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   otherApproval!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   collectiveAgreement!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   opca!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   icomNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   urssafNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   twitter!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   facebook!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   instagram!: string
 
-  @Bool(true, {nullable: false})
+  @Bool(true, { nullable: false })
   portailVisibility!: boolean
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   image!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   creationDate!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   prefectureName!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   prefectureNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   declarationDate!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   tvaNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   schoolCategory!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   typeEstablishment!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   typeEstablishmentDetail!: string
 
-  @Bool(false, {nullable: false})
+  @Bool(false, { nullable: false })
   isPerformanceContractor!: boolean
 
-  @Num(0, {nullable: true})
+  @Num(0, { nullable: true })
   budget!: number
 
-  @Bool(false, {nullable: false})
+  @Bool(false, { nullable: false })
   isPedagogicIsPrincipalActivity!: boolean
 
-  @Num(0, {nullable: true})
+  @Num(0, { nullable: true })
   pedagogicBudget!: number
 }

+ 4 - 4
models/Organization/OrganizationAddressPostal.ts

@@ -1,7 +1,7 @@
-import {Attr, Str, HasOne, Model} from '@vuex-orm/core'
-import {AddressPostal} from "~/models/Core/AddressPostal";
+import { Attr, Str, HasOne, Model } from '@vuex-orm/core'
+import { AddressPostal } from '~/models/Core/AddressPostal'
 
-export class OrganizationAddressPostal extends Model{
+export class OrganizationAddressPostal extends Model {
   static entity = 'organization_address_postals'
 
   @Attr(null)
@@ -10,6 +10,6 @@ export class OrganizationAddressPostal extends Model{
   @HasOne(() => AddressPostal, 'id')
   addressPostal!: AddressPostal | null
 
-  @Str('PRINCIPAL', {nullable: false})
+  @Str('PRINCIPAL', { nullable: false })
   type!: string
 }

+ 6 - 6
models/Organization/OrganizationLicence.ts

@@ -1,20 +1,20 @@
-import {Attr, Str, Model} from '@vuex-orm/core'
+import { Attr, Str, Model } from '@vuex-orm/core'
 
-export class OrganizationLicence extends Model{
+export class OrganizationLicence extends Model {
   static entity = 'organization_licences'
 
   @Attr(null)
   id!: number | null
 
-  @Str('', {nullable: false})
+  @Str('', { nullable: false })
   licensee!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   licenceNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   categorie!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   validityDate!: string
 }

+ 6 - 6
models/Organization/OrganizationNetwork.ts

@@ -1,20 +1,20 @@
-import {Attr, Str, Model} from '@vuex-orm/core'
+import { Attr, Str, Model } from '@vuex-orm/core'
 
-export class OrganizationLicence extends Model{
+export class OrganizationLicence extends Model {
   static entity = 'organization_licences'
 
   @Attr(null)
   id!: number | null
 
-  @Str('', {nullable: false})
+  @Str('', { nullable: false })
   licensee!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   licenceNumber!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   categorie!: string
 
-  @Str('', {nullable: true})
+  @Str('', { nullable: true })
   validityDate!: string
 }

+ 1 - 2
nuxt.config.js

@@ -15,6 +15,5 @@ export default {
   ...plugins,
   ...i18n,
   ...vuetify,
-  ...moment,
+  ...moment
 }
-

+ 48 - 46
package.json

@@ -1,9 +1,9 @@
 {
   "name": "admin",
-  "version": "1.0.0",
+  "version": "0.1.0",
   "private": true,
   "scripts": {
-    "dev": "nuxt",
+    "dev": "nuxt --hostname '0.0.0.0' --port 3002",
     "build": "nuxt build",
     "start": "nuxt start",
     "generate": "nuxt generate",
@@ -13,54 +13,56 @@
     "docs": "jsdoc -c 'jsdoc.json' ./"
   },
   "dependencies": {
-    "@casl/ability": "^5.1.0",
-    "@casl/vue": "^1.2.1",
-    "@nuxt/components": "^2.1.8",
-    "@nuxt/typescript-runtime": "^2.0.0",
-    "@nuxtjs/axios": "^5.13.1",
-    "@nuxtjs/composition-api": "0.24.2",
-    "@types/lodash": "^4.14.168",
+    "@casl/ability": "^5.4",
+    "@casl/vue": "^1.2",
+    "@fortawesome/fontawesome-free": "^5.15",
+    "@nuxt/components": "^2.2",
+    "@nuxt/typescript-runtime": "^2.0",
+    "@nuxtjs/axios": "^5.13",
+    "@nuxtjs/vuetify": "^1.12.1",
+    "@nuxtjs/composition-api": "^0.28",
+    "@nuxtjs/i18n": "^7.0",
+    "@nuxt/image": "^0.6.0",
+    "@types/lodash": "^4.14",
     "@vuex-orm/core": "1.0.0-draft.14",
-    "cookieparser": "^0.1.0",
-    "core-js": "^3.6.5",
-    "js-yaml": "^4.0.0",
-    "lodash": "^4.17.20",
-    "marked": "^1.2.7",
-    "nuxt": "2.15.6",
-    "nuxt-i18n": "6.27.0",
-    "nuxt-leaflet": "^0.0.25",
-    "vue-i18n-composable": "^0.2.1",
-    "vue-template-compiler": "^2.6.13",
-    "yaml-import": "^2.0.0"
+    "cookieparser": "^0.1",
+    "core-js": "^3.17",
+    "js-yaml": "^4.0",
+    "lodash": "^4.17",
+    "marked": "^3.0",
+    "nuxt": "^2.15",
+    "nuxt-leaflet": "^0.0",
+    "postcss": "^8.3.6",
+    "vue-i18n-composable": "^0.2",
+    "vue-template-compiler": "^2.6",
+    "webpack": "^4.46",
+    "yaml-import": "^2.0"
   },
   "devDependencies": {
-    "@fortawesome/fontawesome-free": "^5.15.2",
-    "@nuxt/test-utils": "^0.2.2",
-    "@nuxt/types": "^2.14.6",
-    "@nuxt/typescript-build": "^2.0.3",
-    "@nuxtjs/eslint-config": "^3.1.0",
-    "@nuxtjs/eslint-config-typescript": "^3.0.0",
-    "@nuxtjs/eslint-module": "^2.0.0",
-    "@nuxtjs/moment": "^1.6.1",
-    "@nuxtjs/vuetify": "1.11.3",
-    "@types/jest": "^26.0.23",
-    "@vue/test-utils": "^1.1.0",
+    "@nuxt/test-utils": "^0.2",
+    "@nuxt/types": "^2.15",
+    "@nuxt/typescript-build": "^2.0",
+    "@nuxtjs/eslint-config": "^6.0",
+    "@nuxtjs/eslint-config-typescript": "^6.0",
+    "@nuxtjs/eslint-module": "^3.0",
+    "@nuxtjs/moment": "^1.6",
+    "@types/jest": "^27.0",
+    "@vue/test-utils": "^1.1",
     "babel-core": "7.0.0-bridge.0",
-    "babel-eslint": "^10.1.0",
-    "babel-jest": "^26.5.0",
-    "better-docs": "^2.3.2",
-    "css-loader": "^5.0.0",
-    "eslint": "^7.10.0",
-    "eslint-plugin-nuxt": "^1.0.0",
-    "jest": "^26.5.0",
-    "jsdoc": "^3.6.6",
-    "postcss": "^8.1.10",
-    "postcss-import": "^13.0.0",
-    "postcss-loader": "^4.1.0",
-    "postcss-url": "^10.1.1",
-    "sass": "~1.32.12",
-    "ts-jest": "^26.4.1",
-    "vue-jest": "^3.0.4"
+    "babel-eslint": "^10.1",
+    "babel-jest": "^27.1",
+    "better-docs": "^2.3",
+    "css-loader": "^4.2",
+    "eslint": "^7.32",
+    "eslint-plugin-nuxt": "^2.0",
+    "jest": "^27.1",
+    "jsdoc": "^3.6",
+    "postcss-import": "^13.0",
+    "postcss-loader": "^4.1",
+    "postcss-url": "^10.1",
+    "sass": "^1.32.12",
+    "ts-jest": "^27.0",
+    "vue-jest": "^3.0"
   },
   "resolutions": {
     "@nuxtjs/vuetify/**/sass": "1.32.12"

+ 12 - 9
pages/index.vue

@@ -1,23 +1,26 @@
+<!-- Page d'accueil de l'application -->
+
 <template>
   <v-row justify="center" align="center">
     <v-col cols="12" sm="12" md="12">
       <h3>Bienvenue !</h3>
-      <NuxtLink to="organization">Organization</NuxtLink>
+      <NuxtLink to="organization">
+        Organization
+      </NuxtLink>
     </v-col>
   </v-row>
 </template>
 
-
 <script lang="ts">
-  import {defineComponent} from '@nuxtjs/composition-api'
+import { defineComponent } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    name: 'index',
-    setup() {
-      return {
+export default defineComponent({
+  name: 'Index',
+  setup () {
+    return {
 
-      }
     }
-  })
+  }
+})
 
 </script>

+ 11 - 10
pages/login.vue

@@ -1,3 +1,5 @@
+<!-- Page de login -->
+
 <template>
   <v-row justify="center" align="center">
     <v-col cols="12" sm="12" md="12">
@@ -6,18 +8,17 @@
   </v-row>
 </template>
 
-
 <script lang="ts">
-  import {defineComponent, useContext} from '@nuxtjs/composition-api'
+import { defineComponent } from '@nuxtjs/composition-api'
 
-  export default defineComponent({
-    name: 'login',
-    setup() {
-      return {
+export default defineComponent({
+  name: 'Login',
+  layout: 'login',
+  setup () {
+    return {
 
-      }
-    },
-    layout: 'login'
-  })
+    }
+  }
+})
 
 </script>

+ 42 - 30
pages/organization.vue

@@ -1,43 +1,55 @@
+<!-- Page de détails de l'organization courante -->
+
 <template>
   <LayoutContainer>
+    <!-- Définit le contenu des trois slots du header de la page -->
     <LayoutBannerTop>
-      <template v-slot:bloc1>{{entry.name}}</template>
-      <template v-slot:bloc2>N°Siret : {{entry.siretNumber}}</template>
-      <template v-slot:bloc3>{{entry.description}}</template>
+      <template #block1>
+        {{ entry.name }}
+      </template>
+      <template #block2>
+        N°Siret : {{ entry.siretNumber }}
+      </template>
+      <template #block3>
+        {{ entry.description }}
+      </template>
     </LayoutBannerTop>
+
+    <!-- Rend le contenu de la page -->
     <NuxtChild />
   </LayoutContainer>
 </template>
 
-
 <script lang="ts">
-  import {computed, defineComponent, useContext, ComputedRef} from '@nuxtjs/composition-api'
-  import {QUERY_TYPE} from "~/types/enums";
-  import {Organization} from "~/models/Organization/Organization";
-  import {queryHelper} from "~/services/store/query";
-  import {repositoryHelper} from "~/services/store/repository";
-  import {Item, Query} from "@vuex-orm/core";
+import { computed, defineComponent, useContext, ComputedRef } from '@nuxtjs/composition-api'
+import { Item, Query } from '@vuex-orm/core'
+import { QUERY_TYPE } from '~/types/enums'
+import { Organization } from '~/models/Organization/Organization'
+import { queryHelper } from '~/services/store/query'
+import { repositoryHelper } from '~/services/store/repository'
+
+export default defineComponent({
+  name: 'Organization',
+  setup () {
+    const { store } = useContext()
+
+    const repository = repositoryHelper.getRepository(Organization)
+    const query: ComputedRef<Query> = computed(() => repository.query())
+    const entry: ComputedRef<Item> = computed(() => {
+      return queryHelper.getItem(query.value, store.state.profile.organization.id)
+    })
 
-  export default defineComponent({
-    name: 'organization',
-    setup() {
-      const {store} = useContext()
-      const repository = repositoryHelper.getRepository(Organization)
-      const query:ComputedRef<Query> = computed(() => repository.query())
-      const entry:ComputedRef<Item> = computed(() => {
-        return queryHelper.getItem(query.value, store.state.profile.organization.id)
-      })
-      return {
-        entry
-      }
-    },
-    async asyncData({store, $dataProvider}) {
-      await $dataProvider.invoke({
-        type: QUERY_TYPE.MODEL,
-        model: Organization,
-        id: store.state.profile.organization.id
-      })
+    return {
+      entry
     }
-  })
+  },
+  async asyncData ({ store, $dataProvider }) {
+    await $dataProvider.invoke({
+      type: QUERY_TYPE.MODEL,
+      model: Organization,
+      id: store.state.profile.organization.id
+    })
+  }
+})
 
 </script>

+ 77 - 76
pages/organization/address/_id.vue

@@ -1,66 +1,69 @@
+<!-- Page de détails d'une adresse postale -->
+
 <template>
   <main>
     <v-skeleton-loader
-      type="text"
       v-if="loading"
-    ></v-skeleton-loader>
+      type="text"
+    />
     <LayoutContainer v-else>
       <v-card class="margin-bottom-20">
         <v-toolbar flat class="ot_light_grey toolbarForm" dark dense rounded>
           <v-toolbar-title class="ot_dark_grey--text form_main_title">
-            <v-icon class="ot_white--text ot_green icon">fa-globe-europe</v-icon>
+            <v-icon class="ot_white--text ot_green icon">
+              fa-globe-europe
+            </v-icon>
             {{ $t('address_postal') }}
           </v-toolbar-title>
         </v-toolbar>
 
-        <UiForm :model="model" :id="id" :query="query()">
+        <UiForm :id="id" :model="model" :query="query()">
           <template v-slot:form.input="{entry, updateRepository}">
-
             <v-skeleton-loader
-              type="text"
               v-if="loading"
-            ></v-skeleton-loader>
+              type="text"
+            />
             <v-container v-else fluid class="container">
               <v-row>
                 <v-col cols="12" sm="12">
                   <UiInputAutocomplete
                     field="owner"
                     label="addressOwner"
-                    itemValue="@id"
-                    :itemText="['cp', 'city']"
-                    v-on:update="updateAddressFromOwner"/>
+                    item-value="@id"
+                    :item-text="['cp', 'city']"
+                    @update="updateAddressFromOwner"
+                  />
                 </v-col>
 
                 <v-col cols="12" sm="6">
-                  <UiInputEnum field="type" label="addresstype" :data="entry['type']"  enumType="address_postal_organization" v-on:update="updateRepository"/>
+                  <UiInputEnum field="type" label="addresstype" :data="entry['type']" enum-type="address_postal_organization" @update="updateRepository" />
                 </v-col>
 
                 <v-col cols="12" sm="6">
-                  <UiInputText field="addressPostal.streetAddress" label="streetAddress" :data="entry['addressPostal.streetAddress']" v-on:update="updateRepository"/>
+                  <UiInputText field="addressPostal.streetAddress" label="streetAddress" :data="entry['addressPostal.streetAddress']" @update="updateRepository" />
                 </v-col>
                 <v-col cols="12" sm="6">
-                  <UiInputText field="addressPostal.streetAddressSecond" label="streetAddressSecond" :data="entry['addressPostal.streetAddressSecond']" v-on:update="updateRepository"/>
+                  <UiInputText field="addressPostal.streetAddressSecond" label="streetAddressSecond" :data="entry['addressPostal.streetAddressSecond']" @update="updateRepository" />
                 </v-col>
                 <v-col cols="12" sm="6">
-                  <UiInputText field="addressPostal.streetAddressThird" label="streetAddressThird" :data="entry['addressPostal.streetAddressThird']" v-on:update="updateRepository"/>
+                  <UiInputText field="addressPostal.streetAddressThird" label="streetAddressThird" :data="entry['addressPostal.streetAddressThird']" @update="updateRepository" />
                 </v-col>
                 <v-col cols="12" sm="6">
-                  <UiInputText field="addressPostal.postalCode" label="postalCode" :data="entry['addressPostal.postalCode']" v-on:update="updateRepository"/>
+                  <UiInputText field="addressPostal.postalCode" label="postalCode" :data="entry['addressPostal.postalCode']" @update="updateRepository" />
                 </v-col>
                 <v-col cols="12" sm="6">
-                  <UiInputText field="addressPostal.addressCity" label="addressCity" :data="entry['addressPostal.addressCity']" v-on:update="updateRepository"/>
+                  <UiInputText field="addressPostal.addressCity" label="addressCity" :data="entry['addressPostal.addressCity']" @update="updateRepository" />
                 </v-col>
               </v-row>
             </v-container>
 
-            <UiMap :zoom=12 :address="addressItem" v-on:updateAddress="updateAddress"></UiMap>
-
+            <UiMap :zoom="12" :address="addressItem" @updateAddress="updateAddress" />
           </template>
 
           <template v-slot:form.button>
             <NuxtLink :to="{ path: '/organization', query: { accordion: 'address_postal' }}" class="no-decoration">
               <v-btn class="mr-4 ot_light_grey ot_grey--text">
-                {{$t('back')}}
+                {{ $t('back') }}
               </v-btn>
             </NuxtLink>
           </template>
@@ -70,69 +73,67 @@
   </main>
 </template>
 
-
 <script lang="ts">
-  import {defineComponent, useContext, useFetch, ref, computed, Ref, ComputedRef} from '@nuxtjs/composition-api'
-  import {OrganizationAddressPostal} from "~/models/Organization/OrganizationAddressPostal";
-  import {QUERY_TYPE} from "~/types/enums";
-  import {repositoryHelper} from "~/services/store/repository";
-  import {queryHelper} from "~/services/store/query";
-  import {AnyJson} from "~/types/interfaces";
-  import {AddressPostal} from "~/models/Core/AddressPostal";
-  import {Repository as VuexRepository} from "@vuex-orm/core/dist/src/repository/Repository";
-  import {Model, Query} from "@vuex-orm/core";
-
-  export default defineComponent({
-    name: 'organization_address',
-    setup() {
-      const {route, $dataProvider} = useContext()
-      const loading:Ref<boolean> = ref(true)
-      const id:number = parseInt(route.value.params.id)
-
-      const repository: VuexRepository<Model> = repositoryHelper.getRepository(OrganizationAddressPostal)
-      const query:Query = repository.with('addressPostal', (query) => {
-        query.with('addressCountry')
-      })
-
-      useFetch(async () => {
-        await $dataProvider.invoke({
-          type: QUERY_TYPE.MODEL,
-          model: OrganizationAddressPostal,
-          id: id
-        })
-        loading.value = false
-      })
-
-      const addressItem:ComputedRef<AddressPostal|null> = computed(() =>  {
-        const organizationAddressPostal = queryHelper.getFirstItem(query) as OrganizationAddressPostal
-        return organizationAddressPostal.addressPostal
-      })
-
-      const updateAddress = (address: AddressPostal) => {
-        const organizationAddressPostal = queryHelper.getFirstItem(query) as OrganizationAddressPostal
-        organizationAddressPostal.addressPostal = address
-        repositoryHelper.persist(OrganizationAddressPostal, organizationAddressPostal)
-      }
-
-      /** Computed proprieties needs to be return as function until nuxt3 : https://github.com/nuxt-community/composition-api/issues/207 **/
-      return {
+import { defineComponent, useContext, useFetch, ref, computed, Ref, ComputedRef } from '@nuxtjs/composition-api'
+import { Repository as VuexRepository } from '@vuex-orm/core/dist/src/repository/Repository'
+import { Model, Query } from '@vuex-orm/core'
+import { OrganizationAddressPostal } from '~/models/Organization/OrganizationAddressPostal'
+import { QUERY_TYPE } from '~/types/enums'
+import { repositoryHelper } from '~/services/store/repository'
+import { queryHelper } from '~/services/store/query'
+import { AddressPostal } from '~/models/Core/AddressPostal'
+
+export default defineComponent({
+  name: 'OrganizationAddress',
+  setup () {
+    const { route, $dataProvider } = useContext()
+    const loading: Ref<boolean> = ref(true)
+    const id: number = parseInt(route.value.params.id)
+
+    const repository: VuexRepository<Model> = repositoryHelper.getRepository(OrganizationAddressPostal)
+    const query: Query = repository.with('addressPostal', (query) => {
+      query.with('addressCountry')
+    })
+
+    useFetch(async () => {
+      await $dataProvider.invoke({
+        type: QUERY_TYPE.MODEL,
         model: OrganizationAddressPostal,
-        query: () => query,
-        id,
-        loading,
-        panel: 0,
-        addressItem,
-        updateAddress,
-        updateAddressFromOwner: (data:any) => {
-          console.log(data)
-        }
+        id
+      })
+      loading.value = false
+    })
+
+    const addressItem: ComputedRef<AddressPostal|null> = computed(() => {
+      const organizationAddressPostal = queryHelper.getFirstItem(query) as OrganizationAddressPostal
+      return organizationAddressPostal.addressPostal
+    })
+
+    const updateAddress = (address: AddressPostal) => {
+      const organizationAddressPostal = queryHelper.getFirstItem(query) as OrganizationAddressPostal
+      organizationAddressPostal.addressPostal = address
+      repositoryHelper.persist(OrganizationAddressPostal, organizationAddressPostal)
+    }
+
+    /** Computed properties needs to be returned as functions until nuxt3 : https://github.com/nuxt-community/composition-api/issues/207 **/
+    return {
+      model: OrganizationAddressPostal,
+      query: () => query,
+      id,
+      loading,
+      panel: 0,
+      addressItem,
+      updateAddress,
+      updateAddressFromOwner: (data: any) => {
+        console.log(data)
       }
-    },
-  })
+    }
+  }
+})
 </script>
 <style>
   .toolbarForm .v-toolbar__content{
-    padding-left: 0px !important;
+    padding-left: 0 !important;
   }
   .toolbarForm .v-toolbar__title .v-icon{
     height: 46px;

+ 84 - 80
pages/organization/index.vue

@@ -1,10 +1,14 @@
+<!--
+Contenu de la page pages/organization.vue
+Contient toutes les informations sur l'organization courante
+-->
 <template>
   <LayoutContainer>
 
     <UiForm :model="models.Organization" :id="id" :query="repositories.organizationRepository.query()">
       <template v-slot:form.input="{entry, updateRepository}">
 
-        <v-expansion-panels :value="panel" focusable accordion >
+        <v-expansion-panels :value="panel" focusable accordion>
 
           <!-- Description -->
           <UiExpansionPanel id="description" icon="fa-info">
@@ -305,91 +309,91 @@
 </template>
 
 <script lang="ts">
-  import {defineComponent, useContext} from '@nuxtjs/composition-api'
-  import {$organizationProfile} from "~/services/profile/organizationProfile";
-  import {Organization} from '@/models/Organization/Organization'
-  import {OrganizationAddressPostal} from "~/models/Organization/OrganizationAddressPostal";
-  import {ContactPoint} from "~/models/Core/ContactPoint";
-  import {BankAccount} from "~/models/Core/BankAccount";
-  import {repositoryHelper} from "~/services/store/repository";
-  import UseChecker from "~/use/form/useChecker";
-  import {UseNavigationHelpers} from "~/use/form/useNavigationHelpers";
-
-  export default defineComponent({
-    name: 'organization_parent',
-    setup() {
-      const {store, app: {i18n}, $dataProvider} = useContext()
-      const organizationProfile = $organizationProfile(store)
-      const id:number = store.state.profile.organization.id;
-
-      const repositories = getRepositories()
-
-      const {siretError, siretErrorMessage, checkSiret} = UseChecker.useHandleSiret(i18n, $dataProvider)
-      const checkSiretHook = async (siret:string, field:string, updateRepository:any) => {
-        await checkSiret(siret)
-        if(!siretError.value)
-          updateRepository(siret, field);
-      }
-
-      const {panel} = UseNavigationHelpers.expansionPanels()
-
-      return {
-        repositories,
-        id,
-        organizationProfile,
-        models: {Organization, ContactPoint, BankAccount, OrganizationAddressPostal},
-        datatableHeaders: getDataTablesHeaders(i18n),
-        rules: getRules(i18n),
-        siretError,
-        siretErrorMessage,
-        checkSiretHook,
-        panel
-      }
-    },
-  })
-
-  function getRules(i18n: any) {
-    return {
-      nameRules: [
-        (nameValue: string) => !!nameValue || i18n.t('required'),
-        (nameValue: string) => (nameValue || '').length <= 128 || i18n.t('name_length_rule')
-      ],
-      siretRule: [
-        (siretValue: string) => /^([0-9]{9}|[0-9]{14})$/.test(siretValue) || i18n.t('siret_error')
-      ]
+import {defineComponent, useContext} from '@nuxtjs/composition-api'
+import {$organizationProfile} from "~/services/profile/organizationProfile";
+import {Organization} from '@/models/Organization/Organization'
+import {OrganizationAddressPostal} from "~/models/Organization/OrganizationAddressPostal";
+import {ContactPoint} from "~/models/Core/ContactPoint";
+import {BankAccount} from "~/models/Core/BankAccount";
+import {repositoryHelper} from "~/services/store/repository";
+import UseValidator from "~/use/form/useValidator";
+import {UseNavigationHelpers} from "~/use/form/useNavigationHelpers";
+
+export default defineComponent({
+  name: 'OrganizationParent',
+  setup() {
+    const {store, app: {i18n}, $dataProvider} = useContext()
+    const organizationProfile = $organizationProfile(store)
+    const id: number = store.state.profile.organization.id;
+
+    const repositories = getRepositories()
+
+    const {siretError, siretErrorMessage, checkSiret} = UseValidator.useHandleSiret(i18n, $dataProvider)
+    const checkSiretHook = async (siret: string, field: string, updateRepository: any) => {
+      await checkSiret(siret)
+      if(!siretError.value)
+        updateRepository(siret, field);
     }
-  }
 
-  function getDataTablesHeaders(i18n: any) {
-    return {
-      headersContactPoint: [
-        {text: i18n.t('email'), value: 'email'},
-        {text: i18n.t('telphone'), value: 'telphone'},
-        {text: i18n.t('mobilPhone'), value: 'mobilPhone'},
-        {text: i18n.t('actions'), value: 'actions', sortable: false},
-      ],
-      headersBankAccount: [
-        {text: i18n.t('bankName'), value: 'bankName'},
-        {text: i18n.t('iban'), value: 'iban'},
-        {text: i18n.t('bic'), value: 'bic'},
-        {text: i18n.t('actions'), value: 'actions', sortable: false},
-      ],
-      headersAddressPostal: [
-        {text: i18n.t('address_postal_type'), value: 'type'},
-        {text: i18n.t('address'), value: 'address'},
-        {text: i18n.t('actions'), value: 'actions', sortable: false},
-      ]
-    }
-  }
+    const {panel} = UseNavigationHelpers.expansionPanels()
 
-  function getRepositories() {
     return {
-      organizationRepository: repositoryHelper.getRepository(Organization),
-      contactPointRepository: repositoryHelper.getRepository(ContactPoint),
-      bankAccountRepository: repositoryHelper.getRepository(BankAccount),
-      addressRepository: repositoryHelper.getRepository(OrganizationAddressPostal)
+      repositories,
+      id,
+      organizationProfile,
+      models: {Organization, ContactPoint, BankAccount, OrganizationAddressPostal},
+      datatableHeaders: getDataTablesHeaders(i18n),
+      rules: getRules(i18n),
+      siretError,
+      siretErrorMessage,
+      checkSiretHook,
+      panel
     }
+  },
+})
+
+function getRules(i18n: any) {
+  return {
+    nameRules: [
+      (nameValue: string) => !!nameValue || i18n.t('required'),
+      (nameValue: string) => (nameValue || '').length <= 128 || i18n.t('name_length_rule')
+    ],
+    siretRule: [
+      (siretValue: string) => /^([0-9]{9}|[0-9]{14})$/.test(siretValue) || i18n.t('siret_error')
+    ]
+  }
+}
+
+function getDataTablesHeaders(i18n: any) {
+  return {
+    headersContactPoint: [
+      {text: i18n.t('email'), value: 'email'},
+      {text: i18n.t('telphone'), value: 'telphone'},
+      {text: i18n.t('mobilPhone'), value: 'mobilPhone'},
+      {text: i18n.t('actions'), value: 'actions', sortable: false},
+    ],
+    headersBankAccount: [
+      {text: i18n.t('bankName'), value: 'bankName'},
+      {text: i18n.t('iban'), value: 'iban'},
+      {text: i18n.t('bic'), value: 'bic'},
+      {text: i18n.t('actions'), value: 'actions', sortable: false},
+    ],
+    headersAddressPostal: [
+      {text: i18n.t('address_postal_type'), value: 'type'},
+      {text: i18n.t('address'), value: 'address'},
+      {text: i18n.t('actions'), value: 'actions', sortable: false},
+    ]
+  }
+}
+
+function getRepositories() {
+  return {
+    organizationRepository: repositoryHelper.getRepository(Organization),
+    contactPointRepository: repositoryHelper.getRepository(ContactPoint),
+    bankAccountRepository: repositoryHelper.getRepository(BankAccount),
+    addressRepository: repositoryHelper.getRepository(OrganizationAddressPostal)
   }
+}
 </script>
 
 <style scoped>

+ 321 - 0
pages/organization/subscription.vue

@@ -0,0 +1,321 @@
+<!--
+Page 'Mon abonnement'
+
+@see https://ressources.opentalent.fr/display/SPEC/Mon+abonnement
+-->
+<template>
+  <v-row justify="center" align="center">
+    <v-col cols="12" sm="12" md="12">
+      <h3>{{ $t('my_subscription') }}</h3>
+
+      <v-expansion-panels focusable multiple :value="[0,1]">
+        <UiExpansionPanel id="informations" icon="fa-info">
+          <v-container fluid class="container">
+            <v-row>
+              <v-simple-table>
+                <tbody>
+                  <tr>
+                    <td>{{ $t('client_id') }}</td>
+                    <td></td>
+                  </tr>
+                  <tr>
+                    <td>{{ $t('version') }}</td>
+                    <td>{{ organizationProfile.product }}</td>
+                  </tr>
+                  <tr v-if="organizationProfile.isCmf() && organizationProfile.isArtistProduct()">
+                    <td>{{ $t('paying_structure') }}</td>
+                    <td>
+                      CMF
+                    </td>
+                  </tr>
+                </tbody>
+              </v-simple-table>
+            </v-row>
+          </v-container>
+        </UiExpansionPanel>
+
+        <UiExpansionPanel id="more_features" icon="fa-plus">
+          <v-container id="products-section" fluid class="container">
+            <v-row>
+              <v-simple-table>
+                <template #default>
+                  <thead>
+                    <tr>
+                      <th v-if="organizationProfile.isArtistProduct()">
+                        Opentalent Artist Premium
+                      </th>
+                      <th v-if="organizationProfile.isArtist()">
+                        Opentalent School
+                      </th>
+                      <th v-if="organizationProfile.isSchoolProduct()">
+                        Opentalent School Premium
+                      </th>
+                      <th>
+                        Sms
+                      </th>
+                      <th>
+                        Site internet
+                      </th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td v-if="organizationProfile.isArtistProduct()">
+                        <nuxt-img src="/images/Artist-Square.jpg" />
+                      </td>
+                      <td v-if="organizationProfile.isArtist()">
+                        <nuxt-img src="/images/School-Square.jpg" />
+                      </td>
+                      <td v-if="organizationProfile.isSchoolProduct()">
+                        <nuxt-img src="/images/School-Square.jpg" />
+                      </td>
+                      <td>
+                        <nuxt-img src="/images/sms_big.png" />
+                      </td>
+                      <td>
+                        <nuxt-img src="/images/nom-de-domaine.jpg" />
+                      </td>
+                    </tr>
+                    <tr>
+                      <!-- Opentalent Artist Premium -->
+                      <td v-if="organizationProfile.isArtistProduct()">
+                        <p>
+                          Bénéficiez de plus de fonctionnalités avec la version
+                          <b>Opentalent Artist Premium</b>
+                        </p>
+
+                        <!-- Cmf member -->
+                        <div v-if="organizationProfile.isCmf">
+                          <p>
+                            Pour seulement 6,33 €.TTC par mois *
+                          </p>
+                          <p>
+                            <i>
+                              * Payable annuellement, soit 76 €.TTC / an.
+                              Offre réservée aux adhérents CMF (Prix public: 168€.TTC/an)
+                            </i>
+                          </p>
+                        </div>
+
+                        <!-- Not cmf member -->
+                        <div v-else>
+                          <p>
+                            Pour seulement 14 € TTC par mois *
+                          </p>
+                          <p>
+                            <i>
+                              * Payable annuellement soit 168 €.TTC/an
+                            </i>
+                          </p>
+                        </div>
+
+                        <p>
+                          <a href="https://www.opentalent.fr/uploads/opentalent/ficheproduit_Opentalentartist.pdf">
+                            Fiche produit Opentalent Artist Premium
+                          </a>
+                        </p>
+
+                        <p v-if="organizationProfile.isCmf">
+                          <a href="">
+                            Télécharger le bon de commande CMF
+                          </a>
+                        </p>
+                        <p v-else>
+                          <a href="">
+                            Télécharger le bon de commande
+                          </a>
+                        </p>
+                      </td>
+
+                      <!-- Opentalent School -->
+                      <td v-if="organizationProfile.isArtist()">
+                        <p>
+                          Une solution économique adaptée à votre établissement d'enseignement
+                          artistique
+                        </p>
+
+                        <!-- Cmf member -->
+                        <div v-if="organizationProfile.isCmf">
+                          <p>A partir de 16.45€ TTC par mois *</p>
+                          <p>
+                            <i>
+                              * Payable annuellement, soit 198€ TTC par an
+                              Version <b>Opentalent School Standard</b> jusqu'à 69 élèves.
+                              Hors frais de licence d'utilisation et de formation.
+                              Offre réservée aux adhérents CMF (prix public: 358.80€ TTC par an)
+                            </i>
+                          </p>
+                        </div>
+                        <!-- Not cmf member -->
+                        <div v-else>
+                          <p>A partir de 29.90€ TTC par mois *</p>
+                          <p>
+                            <i>
+                              * Payable annuellement, soit 358 € TTC par an
+                              Version <b>Opentalent School Standard</b> jusqu'à 69 élèves.
+                              Hors frais de licence d'utilisation et de formation.
+                            </i>
+                          </p>
+                        </div>
+
+                        <p>
+                          <a href="https://www.opentalent.fr/uploads/opentalent/ficheproduit_Opentalentschool">
+                            Fiche produit Opentalent School
+                          </a>
+                        </p>
+
+                        <p>
+                          Essayez notre logiciel en toute liberté ! Contactez-nous sans plus tarder pour obtenir
+                          une présentation ainsi qu'un accès de démonstration.
+                        </p>
+                        <p>
+                          Contactez-nous au <a href="tel:+33972126017">0 972 126 017</a>, ou par mail à l'adresse
+                          <a href="mailto:contact@opentalent.fr">contact@opentalent.fr</a>
+                        </p>
+                      </td>
+
+                      <!-- Opentalent School Premium -->
+                      <td v-if="organizationProfile.isSchoolProduct()">
+                        <p>
+                          Passez à la version <b>Opentalent School Premium</b> et bénéficiez:
+                        </p>
+
+                        <ul>
+                          <li>de comptes pour vos professeurs et élèves</li>
+                          <li>d'un site internet complet</li>
+                        </ul>
+
+                        <!-- Cmf member -->
+                        <div v-if="organizationProfile.isCmf">
+                          <p>A partir de 22.30€ TTC par mois *</p>
+                          <p>
+                            <i>* Payable annuellement, soit 267.60€ TTC par an
+                              Version <b>Opentalent School Premium</b> jusqu'à 69 élèves.
+                              Hors frais de licence d'utilisation et de formation.
+                              Offre réservée aux adhérents CMF (prix public: 438€ TTC/an)</i>
+                          </p>
+                        </div>
+                        <!-- Not cmf member -->
+                        <div v-else>
+                          <p>A partir de 36.50€ TTC par mois *</p>
+                          <p>
+                            <i>
+                              * Payable annuellement, soit 438€ TTC par an
+                              Version <b>Opentalent School Premium</b> jusqu'à 69 élèves.
+                              Hors frais de licence d'utilisation et de formation.
+                            </i>
+                          </p>
+                        </div>
+
+                        <p>
+                          <a href="https://www.opentalent.fr/uploads/opentalent/ficheproduit_Opentalentschool">
+                            Fiche produit Opentalent School
+                          </a>
+                        </p>
+
+                        <p>
+                          Contactez-nous au <a href="tel:+33972126017">0-972 126 017</a>, ou par mail à l'adresse
+                          <a href="mailto:contact@opentalent.fr">contact@opentalent.fr</a>
+                        </p>
+                      </td>
+
+                      <!-- SMS -->
+                      <td>
+                        <p>Envoyez des <b>SMS</b> directement depuis votre logiciel à vos membres / élèves</p>
+
+                        <!-- Cmf member -->
+                        <div v-if="organizationProfile.isCmf">
+                          <p>A partir de 0.10 €.TTC/sms *</p>
+                          <p><i>* pour 5000 SMS</i></p>
+
+                          <p>
+                            <a href="https://www.opentalent.fr/uploads/opentalent/Bon_commande_SMS_ouverture-CMF.pdf">
+                              Télécharger le bon de commande CMF
+                            </a>
+                          </p>
+                        </div>
+                        <!-- Not cmf member -->
+                        <div v-else>
+                          <p>A partir de 0.12 €.TTC/sms *</p>
+                          <p><i>* pour 5000 SMS</i></p>
+
+                          <p>
+                            <a href="https://www.opentalent.fr/uploads/opentalent/Bon_commande_SMS_ouverture-public.pdf">
+                              Télécharger le bon de commande
+                            </a>
+                          </p>
+                        </div>
+                      </td>
+
+                      <!-- Custom domain -->
+                      <td>
+                        <p>
+                          Bénéficiez de votre <b>propre nom de domaine</b> et 5 adresses email
+                          pour seulement 34.80 €.TTC / mois
+                        </p>
+
+                        <p>Exemple:</p>
+
+                        <table>
+                          <tr>
+                            <td>Nom de domaine</td>
+                            <td>ma-structure.fr</td>
+                          </tr>
+                          <tr>
+                            <td>Adresse email associée</td>
+                            <td>contact@NomDeMaStructure.fr</td>
+                          </tr>
+                        </table>
+                      </td>
+                    </tr>
+                  </tbody>
+                </template>
+              </v-simple-table>
+            </v-row>
+          </v-container>
+        </UiExpansionPanel>
+      </v-expansion-panels>
+    </v-col>
+  </v-row>
+</template>
+
+<script lang="ts">
+import { defineComponent, useContext } from '@nuxtjs/composition-api'
+import { $organizationProfile } from '~/services/profile/organizationProfile'
+
+export default defineComponent({
+  name: 'Subscription',
+  setup () {
+    const { store } = useContext()
+    const organizationProfile = $organizationProfile(store)
+
+    console.log(organizationProfile)
+
+    return {
+      organizationProfile
+    }
+  }
+})
+</script>
+
+<style>
+  #products-section table {
+    table-layout: fixed;
+    width: 100%;
+  }
+
+  #products-section table img {
+    max-height: 250px;
+    max-width: 100%;
+  }
+
+  #products-section tr:first-child td {
+    text-align: center;
+  }
+
+  #products-section td {
+    padding: 12px 18px;
+    vertical-align: top;
+  }
+
+</style>

+ 3 - 2
plugins/Data/axios.js

@@ -2,8 +2,9 @@ export default function ({ $axios, redirect, store }) {
   $axios.onRequest(config => {
     $axios.setHeader('x-accessid', `${store.state.profile.access.id}`)
 
-    if(store.state.profile.access.switchId)
+    if (store.state.profile.access.switchId) {
       $axios.setHeader('x-switch-user', `${store.state.profile.access.switchId}`)
+    }
 
     $axios.setToken(`${store.state.profile.access.bearer}`, 'Bearer')
   })
@@ -17,7 +18,7 @@ export default function ({ $axios, redirect, store }) {
       redirect('/login')
     }
     if (error.statusCode === 403) {
-      console.debug('forbidden');
+      console.debug('forbidden')
     }
   })
 }

+ 4 - 4
plugins/Data/dataDeleter.ts

@@ -1,11 +1,11 @@
-import {Plugin} from '@nuxt/types'
-import DataDeleter from "~/services/dataDeleter/dataDeleter";
+import { Plugin } from '@nuxt/types'
+import DataDeleter from '~/services/data/dataDeleter'
 
 const dataDeleterPlugin: Plugin = (ctx) => {
-  const dataDeleter = new DataDeleter();
+  const dataDeleter = new DataDeleter()
   dataDeleter.initCtx(ctx)
 
-  //Déclare un nouvel accesseur de service via le context Nuxt
+  // Déclare un nouvel accesseur de service via le context Nuxt
   ctx.$dataDeleter = dataDeleter
 }
 

+ 4 - 4
plugins/Data/dataPersister.ts

@@ -1,11 +1,11 @@
-import {Plugin} from '@nuxt/types'
-import DataPersister from "~/services/dataPersister/dataPersister";
+import { Plugin } from '@nuxt/types'
+import DataPersister from '~/services/data/dataPersister'
 
 const dataPersisterPlugin: Plugin = (ctx) => {
-  const dataPersister = new DataPersister();
+  const dataPersister = new DataPersister()
   dataPersister.initCtx(ctx)
 
-  //Déclare un nouvel accesseur de service via le context Nuxt
+  // Déclare un nouvel accesseur de service via le context Nuxt
   ctx.$dataPersister = dataPersister
 }
 

+ 4 - 4
plugins/Data/dataProvider.ts

@@ -1,11 +1,11 @@
-import {Plugin} from '@nuxt/types'
-import DataProvider from "~/services/dataProvider/dataProvider";
+import { Plugin } from '@nuxt/types'
+import DataProvider from '~/services/data/dataProvider'
 
 const dataProviderPlugin: Plugin = (ctx) => {
-  const dataProvider = new DataProvider();
+  const dataProvider = new DataProvider()
   dataProvider.initCtx(ctx)
 
-  //Déclare un nouvel accesseur de service via le context Nuxt
+  // Déclare un nouvel accesseur de service via le context Nuxt
   ctx.$dataProvider = dataProvider
 }
 

+ 4 - 4
plugins/Rights/ability.ts

@@ -1,6 +1,6 @@
 import { Plugin } from '@nuxt/types'
-import {Ability} from "@casl/ability";
-import {$abilitiesUtils} from "~/services/rights/abilitiesUtils";
+import { Ability } from '@casl/ability'
+import { $abilitiesUtils } from '~/services/rights/abilitiesUtils'
 
 /**
  * Au moment de la phase D'init de Nuxt...
@@ -8,9 +8,9 @@ import {$abilitiesUtils} from "~/services/rights/abilitiesUtils";
  */
 const abilityPlugin: Plugin = (ctx) => {
   $abilitiesUtils(ctx.store, ability).setAbilities()
-  //Déclare un nouvel accesseur de service via le context Nuxt
+  // Déclare un nouvel accesseur de service via le context Nuxt
   ctx.$ability = ability
 }
 
 export default abilityPlugin
-export const ability = new Ability();
+export const ability = new Ability()

+ 4 - 4
plugins/Rights/casl.js

@@ -1,5 +1,5 @@
-import Vue from 'vue';
-import { abilitiesPlugin } from '@casl/vue';
-import { ability } from '@/plugins/Rights/ability';
+import Vue from 'vue'
+import { abilitiesPlugin } from '@casl/vue'
+import { ability } from '@/plugins/Rights/ability'
 
-Vue.use(abilitiesPlugin, ability);
+Vue.use(abilitiesPlugin, ability)

+ 2 - 2
plugins/helpersInit.ts

@@ -1,5 +1,5 @@
-import {Plugin} from '@nuxt/types'
-import {repositoryHelper} from "~/services/store/repository";
+import { Plugin } from '@nuxt/types'
+import { repositoryHelper } from '~/services/store/repository'
 
 const helpersInitPlugin: Plugin = (ctx) => {
   repositoryHelper.setStore(ctx.store)

+ 6 - 6
plugins/route.ts

@@ -1,17 +1,17 @@
-import {Plugin} from '@nuxt/types'
+import { Plugin } from '@nuxt/types'
 
 const routePlugin: Plugin = (ctx) => {
-  if(ctx.app.router){
-    ctx.app.router.beforeEach((to, from, next)=>{
-      if(ctx.store.state.form.dirty){
+  if (ctx.app.router) {
+    ctx.app.router.beforeEach((to, from, next) => {
+      if (ctx.store.state.form.dirty) {
         ctx.store.commit('form/setShowConfirmToLeave', true)
         ctx.store.commit('form/setGoAfterLeave', to)
-      }else{
+      } else {
         next()
       }
     })
 
-    ctx.app.router.afterEach(()=>{
+    ctx.app.router.afterEach(() => {
     })
   }
 }

+ 46 - 44
services/connection/connection.ts

@@ -1,69 +1,71 @@
-import {AnyJson, DataPersisterArgs, DataProviderArgs} from "~/types/interfaces";
-import {HTTP_METHOD} from "~/types/enums";
-import {NuxtAxiosInstance} from "@nuxtjs/axios";
-import {AxiosRequestConfig} from "axios";
+import { NuxtAxiosInstance } from '@nuxtjs/axios'
+import { AxiosRequestConfig } from 'axios'
+import { AnyJson, DataPersisterArgs, DataProviderArgs, UrlArgs } from '~/types/interfaces'
+import { HTTP_METHOD } from '~/types/enums'
 
 /**
  * @category Services/connection
  * @class Connection
- * Classe Wrapper du connecteur de requete (Axios dans notre cas)
+ *
+ * Classe Wrapper du connecteur de requête (Axios dans notre cas)
  */
-class Connection{
-  static connector:NuxtAxiosInstance;
+class Connection {
+  static connector: NuxtAxiosInstance;
 
   /**
    * Initialisation du connecteur (Axios dans notre cas)
    * @param {NuxtAxiosInstance} connector
    */
-  static initConnector(connector: NuxtAxiosInstance){
+  static initConnector (connector: NuxtAxiosInstance) {
     Connection.connector = connector
   }
 
   /**
-   * Main méthode qui apperlera les méthode privées correspondantes (getItem, getCollection, put, post, delete)
-   * @param {HTTP_METHOD} method de requetage
+   * Main méthode qui appellera les méthodes privées correspondantes (getItem, getCollection, put, post, delete)
+   * @param {HTTP_METHOD} method Mode de requêtage (GET, PUT, DELETE...)
    * @param {string} url
    * @param {DataProviderArgs|DataPersisterArgs} args
    * @return {Promise<any>}
    */
-  invoke(method: HTTP_METHOD, url: string, args:DataProviderArgs|DataPersisterArgs): Promise<any>{
+  public static invoke (method: HTTP_METHOD, url: string, args: UrlArgs): Promise<any> {
     switch (method) {
       case HTTP_METHOD.GET:
-        if(args.id)
-          return this.getItem(url, args.id, args.progress)
-        else
-          return this.getCollection(url, args.progress)
+        if (args.id) {
+          return Connection.getItem(url, args.id, args.showProgress)
+        } else {
+          return Connection.getCollection(url, args.showProgress)
+        }
 
       case HTTP_METHOD.PUT:
-        if(this.isArgsIsDataPersisterArgs(args)){
-          if(!args.data)
-            throw new Error('data not found')
-
-          return this.put(url, args.id, args.data, args.progress)
+        if (!Connection.isDataPersisterArgs(args)) {
+          throw new Error('*args* is not a dataPersisterArgs')
         }
-        else throw new Error('args not a dataPersisterArgs')
+        if (!args.data) {
+          throw new Error('*args* has no data')
+        }
+        return Connection.put(url, args.id, args.data, args.showProgress)
 
       case HTTP_METHOD.DELETE:
-        return this.deleteItem(url, args.id, args.progress)
+        return Connection.deleteItem(url, args.id, args.showProgress)
     }
 
-    throw new Error('Method unknown')
+    throw new Error('Unknown connection method was invoked')
   }
 
   /**
    * GET Item : préparation de la config pour la récupération d'un item
    * @param {string} url
    * @param {number} id
-   * @param {boolean} progress
+   * @param {boolean} showProgress
    * @return {Promise<any>}
    */
-  private getItem(url: string, id: number, progress: boolean = true): Promise<any>{
-    const config:AxiosRequestConfig = {
+  public static getItem (url: string, id: number, showProgress: boolean = true): Promise<any> {
+    const config: AxiosRequestConfig = {
       url: `${url}/${id}`,
       method: HTTP_METHOD.GET,
-      progress: progress
+      progress: showProgress
     }
-    return this.request(config)
+    return Connection.request(config)
   }
 
   /**
@@ -72,13 +74,13 @@ class Connection{
    * @param {boolean} progress
    * @return {Promise<any>}
    */
-  private getCollection(url: string, progress: boolean = true): Promise<any>{
-    const config:AxiosRequestConfig = {
+  public static getCollection (url: string, progress: boolean = true): Promise<any> {
+    const config: AxiosRequestConfig = {
       url: `${url}`,
       method: HTTP_METHOD.GET,
-      progress: progress
+      progress
     }
-    return this.request(config)
+    return Connection.request(config)
   }
 
   /**
@@ -89,14 +91,14 @@ class Connection{
    * @param {boolean} progress
    * @return {Promise<any>}
    */
-  private put(url: string, id: number, data: AnyJson, progress: boolean = true): Promise<any>{
-    const config:AxiosRequestConfig = {
+  public static put (url: string, id: number, data: AnyJson, progress: boolean = true): Promise<any> {
+    const config: AxiosRequestConfig = {
       url: `${url}/${id}`,
       method: HTTP_METHOD.PUT,
-      data: data,
-      progress: progress
+      data,
+      progress
     }
-    return this.request(config)
+    return Connection.request(config)
   }
 
   /**
@@ -106,21 +108,21 @@ class Connection{
    * @param {boolean} progress
    * @return {Promise<any>}
    */
-  private deleteItem(url: string, id: number, progress: boolean = true): Promise<any>{
-    const config:AxiosRequestConfig = {
+  public static deleteItem (url: string, id: number, progress: boolean = true): Promise<any> {
+    const config: AxiosRequestConfig = {
       url: `${url}/${id}`,
       method: HTTP_METHOD.DELETE,
-      progress: progress
+      progress
     }
-    return this.request(config)
+    return Connection.request(config)
   }
 
   /**
-   * Exécution de la requete
+   * Exécute la requete
    * @param {AxiosRequestConfig} config
    * @return {Promise<any>}
    */
-  private async request(config:AxiosRequestConfig): Promise<any>{
+  private static async request (config: AxiosRequestConfig): Promise<any> {
     return await Connection.connector.$request(config)
   }
 
@@ -128,7 +130,7 @@ class Connection{
    * Test si l'argument est bien de type DataPersister
    * @param args
    */
-  private isArgsIsDataPersisterArgs(args:DataProviderArgs|DataPersisterArgs): args is DataPersisterArgs{
+  private static isDataPersisterArgs (args: DataProviderArgs|DataPersisterArgs): args is DataPersisterArgs {
     return (args as DataPersisterArgs).data !== undefined
   }
 }

+ 0 - 84
services/connection/constructUrl.ts

@@ -1,84 +0,0 @@
-import {UrlArgs} from "~/types/interfaces";
-import {QUERY_TYPE} from "~/types/enums";
-import {Model} from "@vuex-orm/core";
-import {repositoryHelper} from "~/services/store/repository";
-
-/**
- * @category Services/connection
- * @class ConstructUrl
- * Classe permettant de construire une URL pour l'interrogation d'une API externe
- */
-class ConstructUrl{
-  static ROOT = '/api/'
-
-  /**
-   * Main méthode qui apperlera les méthode privées correspondantes (getDefaultUrl, getEnumUrl, getModelUrl)
-   * @param {UrlArgs} args
-   * @return {string}
-   */
-  invoke(args:UrlArgs): string{
-    switch (args.type) {
-      case QUERY_TYPE.DEFAULT:
-        return this.getDefaultUrl(args.url)
-
-      case QUERY_TYPE.ENUM:
-        return this.getEnumUrl(args.enumType)
-
-      case QUERY_TYPE.MODEL:
-        return this.getModelUrl(args.model, args.root_model, args.root_id)
-
-      default:
-        throw new Error('url, model or enum must be defined');
-    }
-  }
-
-  /**
-   * Construction d'une URL "traditionnelle" qui ira concaténer l'url passée en paramètre avec la ROOT Url définie
-   * @param {string} url
-   * @return {string}
-   */
-  private getDefaultUrl(url?: string): string{
-    if(typeof url === 'undefined')
-      throw new Error('url must be defined');
-    return String(ConstructUrl.ROOT + url).toString()
-  }
-
-  /**
-   * Construction d'une URL Type Enum qui ira concaténer le type enum passé en paramètre avec la ROOT Url définie
-   * @param {string} enumType
-   * @return {string}
-   */
-  private getEnumUrl(enumType?: string): string{
-    if(typeof enumType === 'undefined')
-      throw new Error('enumType must be defined');
-    return String(ConstructUrl.ROOT + 'enum/' + enumType).toString()
-  }
-
-  /**
-   * Construction d'une URL Type Model qui ira concaténer le nom de l'entité du model passé en paramètre
-   * avec la ROOT Url définie (possibilité de récursivité si le root model est défini)
-   *
-   * @param {Model} model roles à tester
-   * @param {Model} rootModel roles à tester
-   * @param {number} rootId roles à tester
-   * @return {string}
-   */
-  private getModelUrl(model?: typeof Model, rootModel?: typeof Model, rootId?: number): string{
-    if(typeof model === 'undefined')
-      throw new Error('model must be defined');
-
-    const entity = repositoryHelper.getEntity(model)
-
-    if(typeof rootModel !== 'undefined'){
-      if(typeof rootId === 'undefined')
-        throw new Error('Root ID must be defined');
-
-      const rootUrl = this.getModelUrl(rootModel) as string
-      return String(`${rootUrl}/${rootId}/${entity}`).toString()
-    }
-
-    return String(ConstructUrl.ROOT  + entity).toString()
-  }
-}
-
-export default ConstructUrl

+ 100 - 0
services/connection/urlBuilder.ts

@@ -0,0 +1,100 @@
+import { Model } from '@vuex-orm/core'
+import { UrlArgs } from '~/types/interfaces'
+import { QUERY_TYPE } from '~/types/enums'
+import { repositoryHelper } from '~/services/store/repository'
+
+/**
+ * Classe permettant de construire une URL pour l'interrogation d'une API externe
+ */
+class UrlBuilder {
+  static ROOT = '/api/'
+
+  /**
+   * Main méthode qui appellera les méthode privées correspondantes (getDefaultUrl, getEnumUrl, getModelUrl)
+   * @param {UrlArgs} args
+   * @return {string}
+   */
+  public static build (args: UrlArgs): string {
+    switch (args.type) {
+      case QUERY_TYPE.DEFAULT:
+        return UrlBuilder.getDefaultUrl(args.url)
+
+      case QUERY_TYPE.ENUM:
+        return UrlBuilder.getEnumUrl(args.enumType)
+
+      case QUERY_TYPE.MODEL:
+        return UrlBuilder.getModelUrl(args.model, args.rootModel, args.rootId)
+
+      default:
+        throw new Error('url, model or enum must be defined')
+    }
+  }
+
+  /**
+   * Construction d'une URL "traditionnelle" qui ira concaténer l'url passée en paramètre avec la ROOT Url définie
+   * @param {string} url
+   * @return {string}
+   */
+  private static getDefaultUrl (url?: string): string {
+    if (typeof url === 'undefined') {
+      throw new TypeError('url must be defined')
+    }
+    return UrlBuilder.concat(UrlBuilder.ROOT, url)
+  }
+
+  /**
+   * Construction d'une URL Type Enum qui ira concaténer le type enum passé en paramètre avec la ROOT Url définie
+   * @param {string} enumType
+   * @return {string}
+   */
+  private static getEnumUrl (enumType?: string): string {
+    if (typeof enumType === 'undefined') {
+      throw new TypeError('enumType must be defined')
+    }
+    return UrlBuilder.concat(UrlBuilder.ROOT, 'enum', enumType)
+  }
+
+  /**
+   * Construction d'une URL Type Model qui ira concaténer le nom de l'entité du model passé en paramètre
+   * avec la ROOT Url définie (possibilité de récursivité si le root model est défini)
+   *
+   * @param {Model} model roles à tester
+   * @param {Model} rootModel roles à tester
+   * @param {number} rootId roles à tester
+   * @return {string}
+   */
+  private static getModelUrl (model?: typeof Model, rootModel?: typeof Model, rootId?: number): string {
+    if (typeof model === 'undefined') {
+      throw new TypeError('model must be defined')
+    }
+
+    const entity = repositoryHelper.getEntity(model)
+
+    if (typeof rootModel !== 'undefined') {
+      if (typeof rootId === 'undefined') {
+        throw new TypeError('Root ID must be defined')
+      }
+
+      const rootUrl = UrlBuilder.getModelUrl(rootModel) as string
+      return String(`${rootUrl}/${rootId}/${entity}`).toString()
+    }
+
+    return UrlBuilder.concat(UrlBuilder.ROOT, entity)
+  }
+
+  /**
+   * Concatenate a base url and a tail
+   * @param base
+   * @param tails
+   * @private
+   */
+  public static concat (base: string, ...tails: string[]): string {
+    let url = base
+    tails.forEach((tail: string) => {
+      url = url.replace(/^|\/$/g, '') + '/' + tail.replace(/^\/?|$/g, '')
+    })
+    return url
+  }
+}
+
+export default UrlBuilder

+ 6 - 4
services/dataProvider/__mocks__/__dataProvider.ts → services/data/__mocks__/__dataProvider.ts

@@ -4,8 +4,10 @@
  cette classe dans l'application...
  */
 const mock = jest.fn().mockImplementation(() => {
-  return {invoke: () => {
+  return {
+    invoke: () => {
       return true
-    }};
-});
-export default mock;
+    }
+  }
+})
+export default mock

+ 76 - 0
services/data/baseDataManager.ts

@@ -0,0 +1,76 @@
+import { Context } from '@nuxt/types/app'
+import { DataManager, UrlArgs } from '~/types/interfaces'
+import Connection from '~/services/connection/connection'
+import Hookable from '~/services/data/hookable'
+import { HTTP_METHOD, QUERY_TYPE } from '~/types/enums'
+import ApiError from '~/services/exception/apiError'
+
+/**
+ * Base class for data providers, persisters or deleters
+ */
+abstract class BaseDataManager extends Hookable implements DataManager {
+  protected ctx!: Context
+  protected arguments!: UrlArgs
+  protected defaultArguments: object = {
+    type: QUERY_TYPE.MODEL,
+    showProgress: true
+  }
+
+  /**
+   * Initialise le contexte (la connection en particulier)
+   * @param ctx
+   */
+  public initCtx (ctx: Context) {
+    Connection.initConnector(ctx.$axios)
+    this.ctx = ctx
+  }
+
+  /**
+   * Exécute la requête et retourne la réponse désérialisée
+   * @param _args
+   */
+  // eslint-disable-next-line require-await
+  protected async _invoke (_args: UrlArgs): Promise<any> {
+    throw new Error('Not implemented')
+  }
+
+  /**
+   * Exécute la requête
+   * @param args
+   */
+  public async invoke (args: UrlArgs): Promise<any> {
+    this.arguments = { ...this.defaultArguments, ...args }
+    BaseDataManager.startLoading(this.arguments)
+
+    await this.triggerHooks(args)
+
+    try {
+      return this._invoke(args)
+    } catch (error) {
+      throw new ApiError(error.response.status, error.response.data.detail)
+    }
+  }
+
+  /**
+   * Signale le début du chargement à nuxt (si showProgress est true)
+   * @param args
+   */
+  private static startLoading (args: UrlArgs) {
+    if (args.showProgress) {
+      const $nuxt = window.$nuxt
+      $nuxt.$loading.start()
+    }
+  }
+
+  /**
+   * Send the request trough the Connection
+   * @param url
+   * @param method
+   * @param args
+   */
+  protected static request (url: string, method: HTTP_METHOD, args: UrlArgs): Promise<any> {
+    return Connection.invoke(method, url, args)
+  }
+}
+
+export default BaseDataManager

+ 33 - 0
services/data/dataDeleter.ts

@@ -0,0 +1,33 @@
+import { DataDeleterArgs } from '~/types/interfaces'
+import BaseDataManager from '~/services/data/baseDataManager'
+import { repositoryHelper } from '~/services/store/repository'
+import { hooksDeleter } from '~/services/data/hooks/hookDeleter/_import'
+import UrlBuilder from '~/services/connection/urlBuilder'
+import { HTTP_METHOD } from '~/types/enums'
+import Connection from '~/services/connection/connection'
+
+/**
+ * Le DataDeleter a pour rôle de supprimer des enregistrements via l'API
+ */
+class DataDeleter extends BaseDataManager {
+  protected arguments!: DataDeleterArgs
+  protected hooks = hooksDeleter;
+
+  /**
+   * Exécute la requête
+   */
+  protected async _invoke (): Promise<any> {
+    // build the url according to the url args
+    const url = UrlBuilder.build(this.arguments)
+
+    // send the DELETE request to the api
+    await Connection.invoke(HTTP_METHOD.DELETE, url, this.arguments)
+
+    // update the store
+    if (this.arguments.model) {
+      await repositoryHelper.deleteItem(this.arguments.model, this.arguments.id)
+    }
+  }
+}
+
+export default DataDeleter

+ 66 - 0
services/data/dataPersister.ts

@@ -0,0 +1,66 @@
+import { AnyJson, DataPersisterArgs, DataProviderArgs } from '~/types/interfaces'
+import UrlBuilder from '~/services/connection/urlBuilder'
+import { DENORMALIZER_TYPE, HTTP_METHOD } from '~/types/enums'
+import Serializer from '~/services/serializer/serializer'
+import DataProvider from '~/services/data/dataProvider'
+import BaseDataManager from '~/services/data/baseDataManager'
+import { hooksPersister } from '~/services/data/hooks/hookPersister/_import'
+
+/**
+ * Le DataPersister a pour rôle de mettre à jour les données via de l'API
+ *
+ * Il sérialise les données et les envoie à l'API sous la forme d'une requête
+ * PUT ou POST, selon les cas. Il fait ensuite appel au DataProvider pour traiter la
+ * réponse de l'API et la retourner.
+ */
+class DataPersister extends BaseDataManager {
+  protected arguments!: DataPersisterArgs
+  protected hooks = hooksPersister;
+
+  /**
+   * Exécute la requête et retourne la réponse désérialisée
+   */
+  protected async _invoke (): Promise<any> {
+    // serialize the data to persist and attach it to the request arguments
+    this.arguments.data = Serializer.normalize(this.arguments)
+
+    // build the url according to the url args
+    const url = UrlBuilder.build(this.arguments)
+
+    // send the POST / PUT request to the api and retrieve the response
+    const response = await DataPersister.request(
+      url,
+      this.arguments.id ? HTTP_METHOD.PUT : HTTP_METHOD.POST,
+      this.arguments
+    )
+
+    // Deserialize, post-process and return the response
+    return await this.provideResponse(response, this.arguments)
+  }
+
+  /**
+   * Use a data provider to deserialize, post-process and return the response
+   *
+   * @param response
+   * @param args
+   */
+  private async provideResponse (response: AnyJson, args: DataPersisterArgs) {
+
+    const deserializedResponse = Serializer.denormalize(response, DENORMALIZER_TYPE.HYDRA)
+
+    const dataProvider = new DataProvider()
+    dataProvider.initCtx(this.ctx)
+    const dataProviderArgs: DataProviderArgs = {
+      type: args.type,
+      url: args.url,
+      enumType: args.enumType,
+      model: args.model,
+      rootModel: args.rootModel,
+      id: args.id,
+      rootId: args.rootId
+    }
+    return await dataProvider.process(deserializedResponse, dataProviderArgs)
+  }
+}
+
+export default DataPersister

+ 69 - 0
services/data/dataProvider.ts

@@ -0,0 +1,69 @@
+import { AnyJson, DataProviderArgs } from '~/types/interfaces'
+import { DENORMALIZER_TYPE, HTTP_METHOD, QUERY_TYPE } from '~/types/enums'
+import { processors } from '~/services/data/processor/_import'
+import UrlBuilder from '~/services/connection/urlBuilder'
+import Serializer from '~/services/serializer/serializer'
+import BaseDataManager from '~/services/data/baseDataManager'
+import { hooksProvider } from '~/services/data/hooks/hookProvider/_import'
+
+/**
+ * Le DataProvider a pour rôle de fournir des données issues de l'API
+ *
+ * Pour cela, le DataProvider envoie une requête GET, récupérer la réponse de l'API,
+ * puis la désérialiser.
+ * Enfin, il va itérer sur les différents Processor disponibles, jusqu'à trouver
+ * un processeur adapté à la requête. Ce processeur va mettre en forme la réponse qui est enfin
+ * retournée par le DataProvider.
+ */
+class DataProvider extends BaseDataManager {
+  protected progress: boolean = false
+  protected arguments!: DataProviderArgs
+  protected hooks = hooksProvider;
+  protected defaultArguments: object = {
+    type: QUERY_TYPE.MODEL,
+    showProgress: false
+  }
+
+  /**
+   * Exécute la requête et retourne la réponse désérialisée
+   */
+  protected async _invoke (): Promise<any> {
+    // build the url according to the url args
+    const url = UrlBuilder.build(this.arguments)
+
+    // send the GET request to the api and retrieve the response
+    const response = await DataProvider.request(url, HTTP_METHOD.GET, this.arguments)
+
+    // deserialize the response
+    const data = await Serializer.denormalize(response, DENORMALIZER_TYPE.HYDRA)
+
+    // post-process the data with the first supported processor
+    return await this.process(data, this.arguments)
+  }
+
+  /**
+   * Find the first supported post data processor, and process the data with it
+   * @param data
+   * @param args
+   */
+  public async process (data: AnyJson, args: DataProviderArgs) {
+    const postProcessor = this.getProcessor(args)
+    return await postProcessor.process(data)
+  }
+
+  /**
+   * Iterate over the available data processors and return an instance of
+   * the first one that support the given args
+   * @param args
+   */
+  private getProcessor (args: DataProviderArgs): any {
+    for (const processor of processors) {
+      if (processor.support(args)) {
+        // eslint-disable-next-line new-cap
+        return new processor(this.ctx, args)
+      }
+    }
+  }
+}
+
+export default DataProvider

+ 35 - 0
services/data/hookable.ts

@@ -0,0 +1,35 @@
+import { UrlArgs } from '~/types/interfaces'
+import BaseHook from '~/services/data/hooks/baseHook'
+
+/**
+ * Base class for an object which support hooks
+ */
+abstract class Hookable {
+  protected hooks: Array<typeof BaseHook> = []; // how could we replace 'any'?
+
+  /**
+   * Iterate over the available hooks and invoke the ones
+   * that support the given args
+   */
+  protected async triggerHooks (args: UrlArgs) {
+    for (const Hook of this.sortedHooks()) {
+      if (Hook.support(args)) {
+        await new Hook().invoke(args)
+      }
+    }
+  }
+
+  /**
+   * Sort the available hooks by priority
+   * @private
+   */
+  private sortedHooks (): Iterable<any> {
+    return this.hooks.sort(function (a, b) {
+      if (a.priority > b.priority) { return 1 }
+      if (a.priority < b.priority) { return -1 }
+      return 0
+    })
+  }
+}
+
+export default Hookable

+ 16 - 0
services/data/hooks/baseHook.ts

@@ -0,0 +1,16 @@
+import { DataProviderArgs } from '~/types/interfaces'
+
+abstract class BaseHook {
+  public static priority = 255
+
+  // eslint-disable-next-line require-await
+  async invoke (_args: DataProviderArgs): Promise<any> {
+    throw new Error('Not implemented')
+  }
+
+  static support (_args: DataProviderArgs): boolean {
+    throw new Error('Not implemented')
+  }
+}
+
+export default BaseHook

+ 5 - 0
services/data/hooks/hookDeleter/_import.ts

@@ -0,0 +1,5 @@
+import HookDeleterExample from '~/services/data/hooks/hookDeleter/hookDeleterExample'
+
+export const hooksDeleter = [
+  HookDeleterExample
+]

+ 18 - 0
services/data/hooks/hookDeleter/hookDeleterExample.ts

@@ -0,0 +1,18 @@
+import BaseHook from '~/services/data/hooks/baseHook'
+import { DataDeleterArgs, HookDeleter } from '~/types/interfaces'
+
+class HookDeleterExample extends BaseHook implements HookDeleter {
+  public static priority = 10
+
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  async invoke (args: DataDeleterArgs): Promise<any> {
+    // eslint-disable-next-line no-console
+    await console.log('This is a deleter hook')
+  }
+
+  static support (_args: DataDeleterArgs): boolean {
+    return false
+  }
+}
+
+export default HookDeleterExample

+ 5 - 0
services/data/hooks/hookPersister/_import.ts

@@ -0,0 +1,5 @@
+import HookPersisterExample from '~/services/data/hooks/hookPersister/hookPersisterExample'
+
+export const hooksPersister = [
+  HookPersisterExample
+]

+ 17 - 0
services/data/hooks/hookPersister/hookPersisterExample.ts

@@ -0,0 +1,17 @@
+import { DataPersisterArgs, HookPersister } from '~/types/interfaces'
+import BaseHook from '~/services/data/hooks/baseHook'
+
+class HookPersisterExample extends BaseHook implements HookPersister {
+  public static  priority = 10
+
+  async invoke (_args: DataPersisterArgs): Promise<any> {
+    // eslint-disable-next-line no-console
+    await console.log('This is a persister hook')
+  }
+
+  static support (_args: DataPersisterArgs): boolean {
+    return false
+  }
+}
+
+export default HookPersisterExample

+ 5 - 0
services/data/hooks/hookProvider/_import.ts

@@ -0,0 +1,5 @@
+import HookProviderExample from '~/services/data/hooks/hookProvider/hookProviderExample'
+
+export const hooksProvider = [
+  HookProviderExample
+]

+ 17 - 0
services/data/hooks/hookProvider/hookProviderExample.ts

@@ -0,0 +1,17 @@
+import BaseHook from '~/services/data/hooks/baseHook'
+import { DataProviderArgs, HookProvider } from '~/types/interfaces'
+
+class HookProviderExample extends BaseHook implements HookProvider {
+  public static  priority = 10
+
+  async invoke (_args: DataProviderArgs): Promise<any> {
+    // eslint-disable-next-line no-console
+    await console.log('This is a provider hook')
+  }
+
+  static support (_args: DataProviderArgs): boolean {
+    return false
+  }
+}
+
+export default HookProviderExample

+ 9 - 0
services/data/processor/_import.ts

@@ -0,0 +1,9 @@
+import ModelProcessor from '~/services/data/processor/modelProcessor'
+import EnumProcessor from '~/services/data/processor/enumProcessor'
+import DefaultProcessor from '~/services/data/processor/defaultProcessor'
+
+export const processors = [
+  DefaultProcessor,
+  ModelProcessor,
+  EnumProcessor
+]

+ 34 - 0
services/data/processor/baseProcessor.ts

@@ -0,0 +1,34 @@
+import { Context } from '@nuxt/types/app'
+import { AnyJson, DataProviderArgs } from '~/types/interfaces'
+import Hookable from '~/services/data/hookable'
+
+class BaseProcessor extends Hookable {
+  protected arguments!: DataProviderArgs;
+  protected ctx!: Context;
+
+  constructor (ctx: Context, args: DataProviderArgs) {
+    super()
+    this.arguments = args
+    this.ctx = ctx
+  }
+
+  /**
+   * Is the given argument a supported model
+   * @param _args
+   */
+  public static support (_args: DataProviderArgs): boolean {
+    throw new Error('Not implemented')
+  }
+
+  /**
+   * Process and return the given data to the Provider
+   *
+   * @param _data
+   */
+  // eslint-disable-next-line require-await
+  public async process (_data: AnyJson): Promise<any> {
+    throw new Error('Not implemented')
+  }
+}
+
+export default BaseProcessor

+ 24 - 0
services/data/processor/defaultProcessor.ts

@@ -0,0 +1,24 @@
+import { AnyJson, DataProviderArgs, Processor } from '~/types/interfaces'
+import BaseProcessor from '~/services/data/processor/baseProcessor'
+import { QUERY_TYPE } from '~/types/enums'
+
+class DefaultProcessor extends BaseProcessor implements Processor {
+  /**
+   * Is the given argument a supported model
+   * @param args
+   */
+  public static support (args: DataProviderArgs): boolean {
+    return args.type === QUERY_TYPE.DEFAULT
+  }
+
+  /**
+   *
+   * @param data
+   */
+  // eslint-disable-next-line require-await
+  async process (data: AnyJson): Promise<any> {
+    return data
+  }
+}
+
+export default DefaultProcessor

+ 48 - 0
services/data/processor/enumProcessor.ts

@@ -0,0 +1,48 @@
+import * as _ from 'lodash'
+import { AnyJson, DataProviderArgs, EnumChoice, EnumChoices, Processor } from '~/types/interfaces'
+import BaseProcessor from '~/services/data/processor/baseProcessor'
+import { QUERY_TYPE } from '~/types/enums'
+
+class EnumProcessor extends BaseProcessor implements Processor {
+
+  /**
+   * Is the given argument a supported model
+   * @param args
+   */
+  public static support (args:DataProviderArgs): boolean {
+    return args.type === QUERY_TYPE.ENUM
+  }
+
+  /**
+   *
+   * @param data
+   */
+  // eslint-disable-next-line require-await
+  async process (data: AnyJson): Promise<any> {
+    const enums: EnumChoices = []
+
+    _.each(data.items, (item, key) => {
+      const entry:EnumChoice = {
+        value: key,
+        label: this.ctx.app.i18n.t(item) as string
+      }
+      enums.push(entry)
+    })
+    return EnumProcessor.sortEnum(enums)
+  }
+
+  /**
+   * Sort the given enum by its elements labels
+   * @param enums
+   * @private
+   */
+  private static sortEnum (enums: EnumChoices) {
+    return enums.sort((a, b) => {
+      if (a.label > b.label) { return 1 }
+      if (a.label < b.label) { return -1 }
+      return 0
+    })
+  }
+}
+
+export default EnumProcessor

+ 32 - 0
services/data/processor/modelProcessor.ts

@@ -0,0 +1,32 @@
+import * as _ from 'lodash'
+import { AnyJson, DataProviderArgs, Processor } from '~/types/interfaces'
+import BaseProcessor from '~/services/data/processor/baseProcessor'
+import { QUERY_TYPE } from '~/types/enums'
+import { repositoryHelper } from '~/services/store/repository'
+
+class ModelProcessor extends BaseProcessor implements Processor {
+  /**
+   * Is the given argument a supported model
+   * @param args
+   */
+  public static support (args: DataProviderArgs): boolean {
+    return args.type === QUERY_TYPE.MODEL
+  }
+
+  /**
+   * Exécute la requête et retourne la réponse désérialisée
+   * @param data
+   */
+  async process (data: AnyJson): Promise<any> {
+    if (typeof this.arguments.model === 'undefined') {
+      throw new TypeError('model must be defined')
+    }
+
+    data.originalState = _.cloneDeep(data)
+    await repositoryHelper.persist(this.arguments.model, data)
+
+    return data
+  }
+}
+
+export default ModelProcessor

+ 0 - 69
services/dataDeleter/dataDeleter.ts

@@ -1,69 +0,0 @@
-import {hooks} from "~/services/dataDeleter/hook/_import";
-import {DataDeleterArgs} from "~/types/interfaces";
-import {Context} from "@nuxt/types/app";
-import Connection from "~/services/connection/connection";
-import ApiError from "~/services/utils/apiError";
-import {HTTP_METHOD} from "~/types/enums";
-import ConstructUrl from "~/services/connection/constructUrl";
-import {repositoryHelper} from "~/services/store/repository";
-
-class DataDeleter{
-  private ctx !: Context
-  private arguments!: DataDeleterArgs
-
-  constructor() {
-    this.sort()
-  }
-
-  initCtx(ctx:Context){
-    Connection.initConnector(ctx.$axios)
-    this.ctx = ctx
-  }
-
-  async invoke(args:DataDeleterArgs): Promise<any>{
-    this.arguments = args
-    try{
-      this.preHook()
-
-      const url = this.constructUrl()
-      const response = await this.connection(url)
-
-      if(this.arguments.model)
-        repositoryHelper.deleteItem(this.arguments.model, this.arguments.id)
-    }catch(error){
-     throw new ApiError(error.response.status, error.response.data.detail)
-    }
-  }
-
-  async preHook(){
-    for(const hook of hooks){
-      if(hook.support(this.arguments)){
-        await new hook().invoke(this.arguments)
-      }
-    }
-  }
-
-  constructUrl(): string{
-    const constructUrl = new ConstructUrl()
-    return constructUrl.invoke(this.arguments)
-  }
-
-  connection(url: string): Promise<any>{
-    const connection = new Connection()
-    return connection.invoke(HTTP_METHOD.DELETE, url, this.arguments)
-  }
-
-  sort(){
-    hooks.sort(function(a, b) {
-      if (a.priority > b.priority) {
-        return 1
-      }
-      if (a.priority < b.priority) {
-        return -1
-      }
-      return 0
-    });
-  }
-}
-
-export default DataDeleter

Некоторые файлы не были показаны из-за большого количества измененных файлов