瀏覽代碼

Merge branch 'V8-5866_run_code_quality_tools' into develop

Olivier Massot 1 年之前
父節點
當前提交
910c9d458d
共有 100 個文件被更改,包括 3306 次插入2915 次删除
  1. 12 0
      .editorconfig
  2. 29 19
      .eslintrc.cjs
  3. 3 1
      .gitlab-ci.yml
  4. 1 0
      .prettierignore
  5. 1 0
      .prettierrc
  6. 17 22
      README.md
  7. 2 2
      assets/css/global.scss
  8. 1 1
      assets/css/settings.scss
  9. 0 1
      assets/css/theme.scss
  10. 12 13
      components/Layout/Alert/Container.vue
  11. 9 11
      components/Layout/Alert/Content.vue
  12. 10 6
      components/Layout/AlertBar.vue
  13. 43 24
      components/Layout/AlertBar/Cotisation.vue
  14. 7 7
      components/Layout/AlertBar/Env.vue
  15. 17 16
      components/Layout/AlertBar/OnlineRegistration.vue
  16. 17 16
      components/Layout/AlertBar/RegistrationStatus.vue
  17. 34 26
      components/Layout/AlertBar/SuperAdmin.vue
  18. 11 8
      components/Layout/AlertBar/SwitchUser.vue
  19. 50 43
      components/Layout/AlertBar/SwitchYear.vue
  20. 10 10
      components/Layout/BannerTop.vue
  21. 3 3
      components/Layout/Container.vue
  22. 39 33
      components/Layout/Dialog.vue
  23. 45 44
      components/Layout/Header.vue
  24. 11 13
      components/Layout/Header/HomeBtn.vue
  25. 57 57
      components/Layout/Header/Menu.vue
  26. 90 85
      components/Layout/Header/Notification.vue
  27. 89 89
      components/Layout/Header/UniversalCreation/Card.vue
  28. 153 137
      components/Layout/Header/UniversalCreation/CreateButton.vue
  29. 72 62
      components/Layout/Header/UniversalCreation/EventParams.vue
  30. 190 178
      components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue
  31. 15 18
      components/Layout/LoadingScreen.vue
  32. 74 75
      components/Layout/MainMenu.vue
  33. 101 98
      components/Layout/ParametersMenu.vue
  34. 45 35
      components/Layout/SubHeader/ActivityYear.vue
  35. 12 12
      components/Layout/SubHeader/Breadcrumbs.vue
  36. 44 30
      components/Layout/SubHeader/DataTiming.vue
  37. 25 18
      components/Layout/SubHeader/DataTimingRange.vue
  38. 45 39
      components/Layout/SubHeader/PersonnalizedList.vue
  39. 55 42
      components/Layout/Subheader.vue
  40. 14 14
      components/Layout/ThemeSwitcher.vue
  41. 27 27
      components/Ui/Button/Delete.vue
  42. 24 23
      components/Ui/Button/Submit.vue
  43. 8 16
      components/Ui/Card.vue
  44. 18 16
      components/Ui/Collection.vue
  45. 17 29
      components/Ui/DataTable.vue
  46. 25 28
      components/Ui/DatePicker.vue
  47. 69 66
      components/Ui/DateRangePicker.vue
  48. 30 30
      components/Ui/ExpansionPanel.vue
  49. 35 35
      components/Ui/Form.vue
  50. 13 21
      components/Ui/Form/Creation.vue
  51. 22 28
      components/Ui/Form/Edition.vue
  52. 10 8
      components/Ui/Help.vue
  53. 58 50
      components/Ui/Image.vue
  54. 92 67
      components/Ui/Input/Autocomplete.vue
  55. 62 50
      components/Ui/Input/Autocomplete/Accesses.vue
  56. 44 45
      components/Ui/Input/AutocompleteWithAPI.vue
  57. 37 28
      components/Ui/Input/AutocompleteWithAp2i.vue
  58. 31 28
      components/Ui/Input/AutocompleteWithEnum.vue
  59. 11 13
      components/Ui/Input/Checkbox.vue
  60. 16 19
      components/Ui/Input/Combobox.vue
  61. 20 22
      components/Ui/Input/DatePicker.vue
  62. 14 18
      components/Ui/Input/Email.vue
  63. 20 21
      components/Ui/Input/Enum.vue
  64. 147 127
      components/Ui/Input/Image.vue
  65. 34 21
      components/Ui/Input/Number.vue
  66. 28 24
      components/Ui/Input/Phone.vue
  67. 33 23
      components/Ui/Input/Text.vue
  68. 23 25
      components/Ui/Input/TextArea.vue
  69. 14 17
      components/Ui/ItemFromUri.vue
  70. 4 14
      components/Ui/LoadingPanel.vue
  71. 38 36
      components/Ui/SystemBar.vue
  72. 6 15
      components/Ui/Template/DataTable.vue
  73. 5 5
      components/Ui/Template/Date.vue
  74. 39 44
      components/Ui/Xeditable/Text.vue
  75. 87 90
      composables/data/useAp2iRequestService.ts
  76. 46 30
      composables/data/useEntityFetch.ts
  77. 9 9
      composables/data/useEntityManager.ts
  78. 8 12
      composables/data/useEnumFetch.ts
  79. 12 11
      composables/data/useEnumManager.ts
  80. 22 17
      composables/data/useImageFetch.ts
  81. 9 9
      composables/data/useImageManager.ts
  82. 55 50
      composables/data/useRefreshProfile.ts
  83. 9 9
      composables/form/useFieldViolation.ts
  84. 20 11
      composables/form/useValidation.ts
  85. 2 3
      composables/form/validation/useSubdomainValidation.ts
  86. 13 8
      composables/layout/useExtensionPanel.ts
  87. 15 9
      composables/layout/useMenu.ts
  88. 14 9
      composables/utils/useAdminUrl.ts
  89. 14 11
      composables/utils/useDownloadFile.ts
  90. 4 5
      composables/utils/useHomeUrl.ts
  91. 10 10
      composables/utils/useI18nUtils.ts
  92. 17 13
      composables/utils/useRedirect.ts
  93. 8 8
      composables/utils/useValidationUtils.ts
  94. 2 3
      config/abilities/config.yaml
  95. 52 31
      config/abilities/pages/addressBook.yaml
  96. 40 31
      config/abilities/pages/admin2ios.yaml
  97. 88 46
      config/abilities/pages/billing.yaml
  98. 27 27
      config/abilities/pages/communication.yaml
  99. 176 101
      config/abilities/pages/cotisations.yaml
  100. 8 5
      config/abilities/pages/donor.yaml

+ 12 - 0
.editorconfig

@@ -0,0 +1,12 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false

+ 29 - 19
.eslintrc.cjs

@@ -2,34 +2,44 @@ module.exports = {
   root: true,
   root: true,
   env: {
   env: {
     browser: true,
     browser: true,
-    node: true
+    node: true,
   },
   },
-  parser: "vue-eslint-parser",
+  parser: 'vue-eslint-parser',
   parserOptions: {
   parserOptions: {
-    "ecmaVersion": 2020,
-    "parser": "@typescript-eslint/parser",
-    "sourceType": "module"
+    ecmaVersion: 2020,
+    parser: '@typescript-eslint/parser',
+    sourceType: 'module',
+    tsconfigRootDir: __dirname,
   },
   },
   extends: [
   extends: [
     '@nuxtjs/eslint-config-typescript',
     '@nuxtjs/eslint-config-typescript',
     'plugin:nuxt/recommended',
     'plugin:nuxt/recommended',
-    "eslint:recommended",
-    "plugin:@typescript-eslint/recommended",
+    'eslint:recommended',
+    'plugin:@typescript-eslint/recommended',
     'plugin:vue/vue3-recommended',
     'plugin:vue/vue3-recommended',
     'plugin:prettier/recommended',
     'plugin:prettier/recommended',
-
-  ],
-  ignorePatterns: [
-    ".nuxt",
-    "coverage/*",
-    "vendor/*",
-    "dist/*"
-  ],
-  plugins: [
-    "vue",
-    "@typescript-eslint"
   ],
   ],
+  ignorePatterns: ['.nuxt', 'coverage/*', 'vendor/*', 'dist/*'],
+  plugins: ['vue', '@typescript-eslint'],
   // add your custom rules here
   // add your custom rules here
   rules: {
   rules: {
-  }
+    'no-console': 0, // on autorise les appels à la console (puisque ceux-ci seront de toute façon nettoyés à la compilation)
+    'vue/valid-v-slot': [
+      'error',
+      {
+        allowModifiers: true,
+      },
+    ],
+  },
+  globals: {
+    useRuntimeConfig: 'readonly',
+    navigateTo: 'readonly',
+    computed: 'readonly',
+    ref: 'readonly',
+    definePageMeta: 'readonly',
+    useRouter: 'readonly',
+    useRoute: 'readonly',
+    useI18n: 'readonly',
+    onMounted: 'readonly',
+  },
 }
 }

+ 3 - 1
.gitlab-ci.yml

@@ -14,7 +14,9 @@ cache:
 
 
 code_quality:
 code_quality:
   script:
   script:
-    - yarn eslint . --env=staging
+    - HOST=ci yarn install --network-timeout 10000
+    - yarn prepare
+    - yarn eslint
 
 
 code_style:
 code_style:
   script:
   script:

+ 1 - 0
.prettierignore

@@ -2,3 +2,4 @@
 coverage
 coverage
 node_modules
 node_modules
 dist
 dist
+assets/css/*.css

+ 1 - 0
.prettierrc

@@ -1,4 +1,5 @@
 {
 {
+  "tabWidth": 2,
   "semi": false,
   "semi": false,
   "singleQuote": true
   "singleQuote": true
 }
 }

+ 17 - 22
README.md

@@ -2,25 +2,23 @@
 
 
 [![Latest Release](http://gitlab.2iopenservice.com/opentalent/app/-/badges/release.svg)](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/-/releases)
 [![Latest Release](http://gitlab.2iopenservice.com/opentalent/app/-/badges/release.svg)](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/-/releases)
 
 
-| Branch  | Status                                                                                                                                                                         | Coverage                                                                                                                                                                       |
-|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Branch  | Status                                                                                                                                                             | Coverage                                                                                                                                                                       |
+| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | master  | [![pipeline status](http://gitlab.2iopenservice.com/opentalent/app/badges/master/pipeline.svg)](http://gitlab.2iopenservice.com/opentalent/app/-/commits/master)   | [![coverage report](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/badges/master/coverage.svg)](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/-/commits/master)   |
 | master  | [![pipeline status](http://gitlab.2iopenservice.com/opentalent/app/badges/master/pipeline.svg)](http://gitlab.2iopenservice.com/opentalent/app/-/commits/master)   | [![coverage report](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/badges/master/coverage.svg)](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/-/commits/master)   |
 | develop | [![pipeline status](http://gitlab.2iopenservice.com/opentalent/app/badges/develop/pipeline.svg)](http://gitlab.2iopenservice.com/opentalent/app/-/commits/develop) | [![coverage report](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/badges/develop/coverage.svg)](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/-/commits/develop) |
 | develop | [![pipeline status](http://gitlab.2iopenservice.com/opentalent/app/badges/develop/pipeline.svg)](http://gitlab.2iopenservice.com/opentalent/app/-/commits/develop) | [![coverage report](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/badges/develop/coverage.svg)](http://gitlab.2iopenservice.com/opentalent/app_nuxt3/-/commits/develop) |
 
 
-
 Frontend Opentalent, avec NuxtJs 3
 Frontend Opentalent, avec NuxtJs 3
 
 
 A voir :
 A voir :
 
 
-* [vuejs.org](https://vuejs.org/guide/introduction.html) : Vue est le framework de base de l'application
-* [nuxtjs.org](https://nuxt.com/docs/getting-started/introduction) : Nuxt est une surcouche à Vue qui automatise et simplifie beaucoup de choses
-* [pinia.vuejs.org](https://pinia.vuejs.org/introduction.html) : Store library that allow you to share a state accross your components / pages
-* [pinia-orm.codedredd.de](https://pinia-orm.codedredd.de/guide/getting-started/quick-start) : Ajoute une gestion par modèles / repos au store Pinia
-* [vuetifyjs.com](https://cdn.vuetifyjs.com/docs/images/logos/vuetify-logo-v3-slim-text-light.svg) : Composants graphiques préconstruits
-* [typescriptlang.org](https://www.typescriptlang.org/) : Typescript
-* [jestjs.io](https://jestjs.io/docs/getting-started) : Tests unitaires pour JS
-* [cypress.io](https://docs.cypress.io/guides/getting-started/installing-cypress) : Tests end-to-end pour JS
-
+- [vuejs.org](https://vuejs.org/guide/introduction.html) : Vue est le framework de base de l'application
+- [nuxtjs.org](https://nuxt.com/docs/getting-started/introduction) : Nuxt est une surcouche à Vue qui automatise et simplifie beaucoup de choses
+- [pinia.vuejs.org](https://pinia.vuejs.org/introduction.html) : Store library that allow you to share a state accross your components / pages
+- [pinia-orm.codedredd.de](https://pinia-orm.codedredd.de/guide/getting-started/quick-start) : Ajoute une gestion par modèles / repos au store Pinia
+- [vuetifyjs.com](https://cdn.vuetifyjs.com/docs/images/logos/vuetify-logo-v3-slim-text-light.svg) : Composants graphiques préconstruits
+- [typescriptlang.org](https://www.typescriptlang.org/) : Typescript
+- [jestjs.io](https://jestjs.io/docs/getting-started) : Tests unitaires pour JS
+- [cypress.io](https://docs.cypress.io/guides/getting-started/installing-cypress) : Tests end-to-end pour JS
 
 
 ## Installation (mode dev)
 ## Installation (mode dev)
 
 
@@ -28,23 +26,19 @@ Cloner le projet :
 
 
     git clone git@gitlab.2iopenservice.com:opentalent/app.git
     git clone git@gitlab.2iopenservice.com:opentalent/app.git
 
 
-
 Installer les dépendances :
 Installer les dépendances :
 
 
     yarn install
     yarn install
 
 
-
 Copier les certificats dans le répertoire `env/` de ce projet :
 Copier les certificats dans le répertoire `env/` de ce projet :
 
 
-* local.app.opentalent.fr.crt
-* local.app.opentalent.fr.key
-
+- local.app.opentalent.fr.crt
+- local.app.opentalent.fr.key
 
 
 Lancer le serveur de développement :
 Lancer le serveur de développement :
 
 
     yarn dev -o
     yarn dev -o
 
 
-
 ## Déploiement en prod
 ## Déploiement en prod
 
 
 ### Premier déploiement en tant que service
 ### Premier déploiement en tant que service
@@ -73,7 +67,6 @@ Attention, sur les environnements de test, il faut utiliser nvm pour exécuter l
 
 
     nvm exec yarn install
     nvm exec yarn install
 
 
-
 ## Autres
 ## Autres
 
 
 ### Lancer les tests
 ### Lancer les tests
@@ -97,6 +90,10 @@ Sur les environnements où app est servie par supervisor, on peut consulter les
 > le `-6000` étant le nombre de bytes à afficher
 > le `-6000` étant le nombre de bytes à afficher
 > Voir plus : http://supervisord.org/running.html#supervisorctl-command-line-options
 > Voir plus : http://supervisord.org/running.html#supervisorctl-command-line-options
 
 
+### Exécuter ESLint
+
+    yarn lint
+
 ### Faire fonctionner le HMR
 ### Faire fonctionner le HMR
 
 
 Si le HMR (Hot Module Reload) ne fontionne pas et qu'un message d'erreur est logué en console disant que l'adresse
 Si le HMR (Hot Module Reload) ne fontionne pas et qu'un message d'erreur est logué en console disant que l'adresse
@@ -108,13 +105,12 @@ n'est pas accessible, alors suivre les étapes suivantes :
 - Clic droit dessus, puis "ouvrir dans un nouvel onglet"
 - Clic droit dessus, puis "ouvrir dans un nouvel onglet"
 - Ajouter une exception de sécurité dans le navigateur
 - Ajouter une exception de sécurité dans le navigateur
 
 
-
 ## Plus d'infos
 ## Plus d'infos
 
 
 ## Structure du projet
 ## Structure du projet
 
 
 | Répertoire     | Rôle                                                                                                |
 | Répertoire     | Rôle                                                                                                |
-|----------------|-----------------------------------------------------------------------------------------------------|
+| -------------- | --------------------------------------------------------------------------------------------------- |
 | `assets`       | Contient les fichiers style et medias                                                               |
 | `assets`       | Contient les fichiers style et medias                                                               |
 | `components`   | Les différents composants graphiques qui composent l'application                                    |
 | `components`   | Les différents composants graphiques qui composent l'application                                    |
 | `composables`  | Des fonctions conscientes du contexte applicatif, qui font le lien entre les pages et les services  |
 | `composables`  | Des fonctions conscientes du contexte applicatif, qui font le lien entre les pages et les services  |
@@ -132,4 +128,3 @@ n'est pas accessible, alors suivre les étapes suivantes :
 | `stores`       | Le store et ses composants servent d'entrepôt de donnés, et s'assurent de la cohérence de celles-ci |
 | `stores`       | 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...)                                                       |
 | `tests`        | Regroupe les tests (unitaires, end-to-end...)                                                       |
 | `types`        | Types Typescript (interfaces, enums...)                                                             |
 | `types`        | Types Typescript (interfaces, enums...)                                                             |
-

File diff suppressed because it is too large
+ 2 - 2
assets/css/global.scss


+ 1 - 1
assets/css/settings.scss

@@ -2,4 +2,4 @@
 @forward 'vuetify/settings' with (
 @forward 'vuetify/settings' with (
   $button-color: green,
   $button-color: green,
   $button-font-weight: 700
   $button-font-weight: 700
-);
+);

+ 0 - 1
assets/css/theme.scss

@@ -1,4 +1,3 @@
-
 .theme-primary {
 .theme-primary {
   background-color: rgb(var(--v-theme-primary)) !important;
   background-color: rgb(var(--v-theme-primary)) !important;
   color: rgb(var(--v-theme-on-primary)) !important;
   color: rgb(var(--v-theme-on-primary)) !important;

+ 12 - 13
components/Layout/Alert/Container.vue

@@ -17,27 +17,26 @@ Container principal pour l'affichage d'une ou plusieurs alertes
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import type { Alert } from '~/types/interfaces'
 import type { Alert } from '~/types/interfaces'
-import {usePageStore} from "~/stores/page";
-import type {ComputedRef} from "@vue/reactivity";
+import { usePageStore } from '~/stores/page'
+import type { ComputedRef } from '@vue/reactivity'
 
 
 const pageStore = usePageStore()
 const pageStore = usePageStore()
 
 
 const alerts: ComputedRef<Array<Alert>> = computed(() => {
 const alerts: ComputedRef<Array<Alert>> = computed(() => {
   return pageStore.alerts
   return pageStore.alerts
 })
 })
-
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-  .alertContainer {
-    position: fixed;
-    bottom: 0;
-    right: 20px;
-    z-index: 1000;
-  }
+.alertContainer {
+  position: fixed;
+  bottom: 0;
+  right: 20px;
+  z-index: 1000;
+}
 
 
-  .alertContainer > .alertContent {
-    position: relative;
-    margin-bottom: 10px;
-  }
+.alertContainer > .alertContent {
+  position: relative;
+  margin-bottom: 10px;
+}
 </style>
 </style>

+ 9 - 11
components/Layout/Alert/Content.vue

@@ -13,20 +13,20 @@
     @mouseout="onMouseOut"
     @mouseout="onMouseOut"
   >
   >
     <ul v-if="props.alert.messages.length > 1">
     <ul v-if="props.alert.messages.length > 1">
-       <li v-for="message in props.alert.messages">
+      <li v-for="message in props.alert.messages">
         {{ $t(message) }}
         {{ $t(message) }}
       </li>
       </li>
     </ul>
     </ul>
     <span v-else>
     <span v-else>
-        {{ $t(props.alert.messages[0]) }}
+      {{ $t(props.alert.messages[0]) }}
     </span>
     </span>
   </v-alert>
   </v-alert>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import type {Alert} from '~/types/interfaces'
-import type {Ref} from "@vue/reactivity";
-import {usePageStore} from "~/stores/page";
+import type { Alert } from '~/types/interfaces'
+import type { Ref } from '@vue/reactivity'
+import { usePageStore } from '~/stores/page'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -34,7 +34,7 @@ const props = defineProps({
    */
    */
   alert: {
   alert: {
     type: Object as () => Alert,
     type: Object as () => Alert,
-    required: true
+    required: true,
   },
   },
   /**
   /**
    * The time after which the alert disappears
    * The time after which the alert disappears
@@ -42,8 +42,8 @@ const props = defineProps({
   timeout: {
   timeout: {
     type: Number,
     type: Number,
     required: false,
     required: false,
-    default: 3000
-  }
+    default: 3000,
+  },
 })
 })
 
 
 const show: Ref<boolean> = ref(true)
 const show: Ref<boolean> = ref(true)
@@ -77,8 +77,6 @@ const onMouseOut = () => {
 }
 }
 
 
 clearAlert()
 clearAlert()
-
 </script>
 </script>
 
 
-<style scoped>
-</style>
+<style scoped></style>

+ 10 - 6
components/Layout/AlertBar.vue

@@ -11,19 +11,23 @@ Contient les différentes barres d'alertes qui s'affichent dans certains cas
     <LayoutAlertBarSwitchUser />
     <LayoutAlertBarSwitchUser />
 
 
     <client-only>
     <client-only>
-      <LayoutAlertBarCotisation v-if="organizationProfile.isCmf && ability.can('manage', 'cotisation')" />
+      <LayoutAlertBarCotisation
+        v-if="organizationProfile.isCmf && ability.can('manage', 'cotisation')"
+      />
     </client-only>
     </client-only>
 
 
     <LayoutAlertBarSwitchYear />
     <LayoutAlertBarSwitchYear />
     <LayoutAlertBarSuperAdmin />
     <LayoutAlertBarSuperAdmin />
-    <LayoutAlertBarRegistrationStatus v-if="organizationProfile.hasModule('IEL')" />
+    <LayoutAlertBarRegistrationStatus
+      v-if="organizationProfile.hasModule('IEL')"
+    />
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-  import {useAbility} from "@casl/vue";
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
+import { useAbility } from '@casl/vue'
 
 
-  const organizationProfile = useOrganizationProfileStore()
-  const ability = useAbility()
+const organizationProfile = useOrganizationProfileStore()
+const ability = useAbility()
 </script>
 </script>

+ 43 - 24
components/Layout/AlertBar/Cotisation.vue

@@ -7,22 +7,22 @@ Barre d'alerte qui s'affiche pour donner l'état de la cotisation
 <template>
 <template>
   <main>
   <main>
     <UiSystemBar
     <UiSystemBar
-        v-if="alert && alert.text && alert.callback"
-        :text="$t(alert.text)"
-        icon="fas fa-info-circle"
-        :on-click="alert.callback"
-        class="theme-info"
+      v-if="alert && alert.text && alert.callback"
+      :text="$t(alert.text)"
+      icon="fas fa-info-circle"
+      :on-click="alert.callback"
+      class="theme-info"
     />
     />
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import type {Ref} from "vue";
-import UrlUtils from "~/services/utils/urlUtils";
-import {ALERT_STATE_COTISATION} from "~/types/enum/enums";
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import Cotisation from "~/models/Organization/Cotisation";
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
+import type { Ref } from 'vue'
+import UrlUtils from '~/services/utils/urlUtils'
+import { ALERT_STATE_COTISATION } from '~/types/enum/enums'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import Cotisation from '~/models/Organization/Cotisation'
 
 
 const organizationProfile = useOrganizationProfileStore()
 const organizationProfile = useOrganizationProfileStore()
 
 
@@ -38,7 +38,12 @@ const goToCotisation = () => {
   if (!organizationProfile.id) {
   if (!organizationProfile.id) {
     throw new Error('missing organization id')
     throw new Error('missing organization id')
   }
   }
-  window.location.href = UrlUtils.join(baseLegacyUrl, '/cotisation/cotisation_steps', organizationProfile.id, 'steps/1')
+  window.location.href = UrlUtils.join(
+    baseLegacyUrl,
+    '/cotisation/cotisation_steps',
+    organizationProfile.id,
+    'steps/1',
+  )
 }
 }
 
 
 /**
 /**
@@ -48,21 +53,30 @@ const openInvoiceWindow = () => {
   if (!cotisationYear.value) {
   if (!cotisationYear.value) {
     throw new Error('no cotisation year defined')
     throw new Error('no cotisation year defined')
   }
   }
-  window.open(UrlUtils.join(baseLegacyUrl, 'cotisation/invoice', cotisationYear.value), '_blank')
+  window.open(
+    UrlUtils.join(baseLegacyUrl, 'cotisation/invoice', cotisationYear.value),
+    '_blank',
+  )
 }
 }
 
 
 /**
 /**
  * Redirige l'utilisateur vers la page des assurances
  * Redirige l'utilisateur vers la page des assurances
  */
  */
 const goToInsurancePage = () => {
 const goToInsurancePage = () => {
-  window.location.href = UrlUtils.join(baseLegacyUrl, 'cotisation/insuranceedit')
+  window.location.href = UrlUtils.join(
+    baseLegacyUrl,
+    'cotisation/insuranceedit',
+  )
 }
 }
 
 
 /**
 /**
  * Redirige (dans un nouvel onglet) l'utilsateur vers le site web de la CMF
  * Redirige (dans un nouvel onglet) l'utilsateur vers le site web de la CMF
  */
  */
 const openCmfSubscriptionPage = () => {
 const openCmfSubscriptionPage = () => {
-  window.open('https://www.cmf-musique.org/services/assurances/assurance-de-groupe/', '_blank')
+  window.open(
+    'https://www.cmf-musique.org/services/assurances/assurance-de-groupe/',
+    '_blank',
+  )
 }
 }
 
 
 // On récupère l'état des cotisations via l'API
 // On récupère l'état des cotisations via l'API
@@ -71,7 +85,10 @@ if (!organizationProfile.id) {
 }
 }
 
 
 const { fetch } = useEntityFetch()
 const { fetch } = useEntityFetch()
-const { data: cotisation, pending } = await fetch(Cotisation, organizationProfile.id)
+const { data: cotisation, pending } = await fetch(
+  Cotisation,
+  organizationProfile.id,
+)
 
 
 interface Alert {
 interface Alert {
   text: string
   text: string
@@ -86,10 +103,13 @@ const alert: ComputedRef<Alert | null> = computed(() => {
   cotisationYear.value = cotisation.value.cotisationYear
   cotisationYear.value = cotisation.value.cotisationYear
 
 
   const mapping: Record<ALERT_STATE_COTISATION, Alert> = {
   const mapping: Record<ALERT_STATE_COTISATION, Alert> = {
-    'AFFILIATION': { text: 'cotisation_access', callback: goToCotisation },
-    'INVOICE': { text: 'upload_cotisation_invoice', callback: openInvoiceWindow },
-    'INSURANCE': { text: 'renew_insurance_cmf', callback: goToInsurancePage },
-    'ADVERTISINGINSURANCE': { text: 'insurance_cmf_subscription', callback: openCmfSubscriptionPage },
+    AFFILIATION: { text: 'cotisation_access', callback: goToCotisation },
+    INVOICE: { text: 'upload_cotisation_invoice', callback: openInvoiceWindow },
+    INSURANCE: { text: 'renew_insurance_cmf', callback: goToInsurancePage },
+    ADVERTISINGINSURANCE: {
+      text: 'insurance_cmf_subscription',
+      callback: openCmfSubscriptionPage,
+    },
   }
   }
 
 
   if (!cotisation.value.alertState) {
   if (!cotisation.value.alertState) {
@@ -98,11 +118,10 @@ const alert: ComputedRef<Alert | null> = computed(() => {
 
 
   return mapping[cotisation.value.alertState as ALERT_STATE_COTISATION]
   return mapping[cotisation.value.alertState as ALERT_STATE_COTISATION]
 })
 })
-
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  :deep(.clickable:hover) {
-    text-decoration: none !important;
-  }
+:deep(.clickable:hover) {
+  text-decoration: none !important;
+}
 </style>
 </style>

+ 7 - 7
components/Layout/AlertBar/Env.vue

@@ -6,16 +6,16 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur n'est pas dans un environneme
 
 
 <template>
 <template>
   <UiSystemBar
   <UiSystemBar
-      v-if="show"
-      :text="$t('not_production_environment', { env: env })"
-      icon="fas fa-exclamation-triangle"
-      class="theme-warning"
+    v-if="show"
+    :text="$t('not_production_environment', { env: env })"
+    icon="fas fa-exclamation-triangle"
+    class="theme-warning"
   />
   />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  const runtimeConfig = useRuntimeConfig()
+const runtimeConfig = useRuntimeConfig()
 
 
-  const env = runtimeConfig.public.env ?? 'unknown'
-  const show = env !== 'production'
+const env = runtimeConfig.public.env ?? 'unknown'
+const show = env !== 'production'
 </script>
 </script>

+ 17 - 16
components/Layout/AlertBar/OnlineRegistration.vue

@@ -5,37 +5,38 @@ Barre d'alerte sur l'ouverture ou non de l'inscription en ligne
 
 
 <template>
 <template>
   <UiSystemBar
   <UiSystemBar
-      v-if="show"
-      :text="$t(message)"
-      icon="fas fa-id-card"
-      class="theme-secondary-alt"
+    v-if="show"
+    :text="$t(message)"
+    icon="fas fa-id-card"
+    class="theme-secondary-alt"
   />
   />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import RegistrationAvailability from "~/models/OnlineRegistration/RegistrationAvailability";
-import {ComputedRef} from "@vue/reactivity";
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import RegistrationAvailability from '~/models/OnlineRegistration/RegistrationAvailability'
+import { ComputedRef } from '@vue/reactivity'
 
 
 const { fetch } = useEntityFetch()
 const { fetch } = useEntityFetch()
 
 
 const accessProfile = useAccessProfileStore()
 const accessProfile = useAccessProfileStore()
 
 
-const { data: registrationAvailability, pending } = fetch(RegistrationAvailability, accessProfile.id ?? 0)
+const { data: registrationAvailability, pending } = fetch(
+  RegistrationAvailability,
+  accessProfile.id ?? 0,
+)
 
 
 const show: ComputedRef<boolean> = computed(() => {
 const show: ComputedRef<boolean> = computed(() => {
-  return !pending && (registrationAvailability.value as RegistrationAvailability).available
+  return (
+    !pending &&
+    (registrationAvailability.value as RegistrationAvailability).available
+  )
 })
 })
 
 
 const message: ComputedRef<string> = computed(() => {
 const message: ComputedRef<string> = computed(() => {
   return (registrationAvailability.value as RegistrationAvailability).message
   return (registrationAvailability.value as RegistrationAvailability).message
 })
 })
-
-
 </script>
 </script>
 
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 17 - 16
components/Layout/AlertBar/RegistrationStatus.vue

@@ -5,30 +5,33 @@ Barre d'alerte quand au statut (l'avancement) de l'inscription en ligne de l'uti
 
 
 <template>
 <template>
   <UiSystemBar
   <UiSystemBar
-      v-if="!pending && message"
-      :text="$t(message)"
-      icon="fas fa-id-card"
-      class="theme-secondary"
+    v-if="!pending && message"
+    :text="$t(message)"
+    icon="fas fa-id-card"
+    class="theme-secondary"
   />
   />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import RegistrationStatus from "~/models/OnlineRegistration/RegistrationStatus";
-import type {ComputedRef} from "@vue/reactivity";
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import RegistrationStatus from '~/models/OnlineRegistration/RegistrationStatus'
+import type { ComputedRef } from '@vue/reactivity'
 
 
 const { fetch } = useEntityFetch()
 const { fetch } = useEntityFetch()
 
 
 const accessProfile = useAccessProfileStore()
 const accessProfile = useAccessProfileStore()
 
 
-const { data: registrationStatus, pending } = fetch(RegistrationStatus, accessProfile.id ?? 0)
+const { data: registrationStatus, pending } = fetch(
+  RegistrationStatus,
+  accessProfile.id ?? 0,
+)
 
 
 const messagesByStatus = {
 const messagesByStatus = {
-  'NEGOTIABLE': "your_application_is_awaiting_processing",
-  'PENDING': "you_have_been_placed_on_the_waiting_list",
-  'ACCEPTED': "your_registration_file_has_been_validated",
-  'DENIED': "your_application_has_been_refused",
+  NEGOTIABLE: 'your_application_is_awaiting_processing',
+  PENDING: 'you_have_been_placed_on_the_waiting_list',
+  ACCEPTED: 'your_registration_file_has_been_validated',
+  DENIED: 'your_application_has_been_refused',
 }
 }
 
 
 const message: ComputedRef<string> = computed(() => {
 const message: ComputedRef<string> = computed(() => {
@@ -40,6 +43,4 @@ const message: ComputedRef<string> = computed(() => {
 })
 })
 </script>
 </script>
 
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 34 - 26
components/Layout/AlertBar/SuperAdmin.vue

@@ -13,33 +13,41 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur est un super admin en mode sw
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {useAccessProfileStore} from "~/stores/accessProfile";
-  import UrlUtils from "~/services/utils/urlUtils";
-  import type {ComputedRef} from "@vue/reactivity";
-  import {navigateTo} from "#app";
-  import {useAdminUrl} from "~/composables/utils/useAdminUrl";
-
-  const { makeAdminUrl } = useAdminUrl()
-
-  const accessProfile = useAccessProfileStore()
-
-  const show: ComputedRef<boolean> = computed(() =>
-      accessProfile.originalAccess !== null && accessProfile.originalAccess.isSuperAdminAccess
-  )
-
-  const url: ComputedRef<string> = computed(() => {
-    const orgId = accessProfile.originalAccess ? accessProfile.originalAccess.organization.id : null
-    const originalAccessId = accessProfile.originalAccess ? accessProfile.originalAccess.id : null
-
-    if (show && orgId && originalAccessId) {
-      return makeAdminUrl(UrlUtils.join('#', 'switch_user', orgId, originalAccessId, 'exit'))
-    }
-    return ''
-  })
-
-  const onClick = () => {
-    navigateTo(url.value,{ external: true })
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import UrlUtils from '~/services/utils/urlUtils'
+import type { ComputedRef } from '@vue/reactivity'
+import { navigateTo } from '#app'
+import { useAdminUrl } from '~/composables/utils/useAdminUrl'
+
+const { makeAdminUrl } = useAdminUrl()
+
+const accessProfile = useAccessProfileStore()
+
+const show: ComputedRef<boolean> = computed(
+  () =>
+    accessProfile.originalAccess !== null &&
+    accessProfile.originalAccess.isSuperAdminAccess,
+)
+
+const url: ComputedRef<string> = computed(() => {
+  const orgId = accessProfile.originalAccess
+    ? accessProfile.originalAccess.organization.id
+    : null
+  const originalAccessId = accessProfile.originalAccess
+    ? accessProfile.originalAccess.id
+    : null
+
+  if (show && orgId && originalAccessId) {
+    return makeAdminUrl(
+      UrlUtils.join('#', 'switch_user', orgId, originalAccessId, 'exit'),
+    )
   }
   }
+  return ''
+})
+
+const onClick = () => {
+  navigateTo(url.value, { external: true })
+}
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 11 - 8
components/Layout/AlertBar/SwitchUser.vue

@@ -8,23 +8,26 @@ Barre qui s'affiche lorsque l'utilisateur possède un compte multi user
   <UiSystemBar v-if="show" class="theme-info">
   <UiSystemBar v-if="show" class="theme-info">
     <v-icon small icon="fas fa-info-circle" />
     <v-icon small icon="fas fa-info-circle" />
     <span>
     <span>
-      {{ $t('multi_account_alert_part1') }} <strong>{{ fullName }}</strong> {{ $t('multi_account_alert_part2') }}
+      {{ $t('multi_account_alert_part1') }} <strong>{{ fullName }}</strong>
+      {{ $t('multi_account_alert_part2') }}
     </span>
     </span>
 
 
-    <v-icon class="pl-1" small icon="fa fa-users"/> &nbsp;{{$t('multi_account_alert_part3')}}
+    <v-icon class="pl-1" small icon="fa fa-users" /> &nbsp;{{
+      $t('multi_account_alert_part3')
+    }}
   </UiSystemBar>
   </UiSystemBar>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {useAccessProfileStore} from "~/stores/accessProfile";
-  import {useMenu} from "~/composables/layout/useMenu";
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import { useMenu } from '~/composables/layout/useMenu'
 
 
-  const accessProfile = useAccessProfileStore()
-  const { hasMenu } = useMenu()
+const accessProfile = useAccessProfileStore()
+const { hasMenu } = useMenu()
 
 
-  const show = computed(() => hasMenu('MyFamily'))
+const show = computed(() => hasMenu('MyFamily'))
 
 
-  const fullName = `${accessProfile.givenName} ${accessProfile.name}`
+const fullName = `${accessProfile.givenName} ${accessProfile.name}`
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 50 - 43
components/Layout/AlertBar/SwitchYear.vue

@@ -7,63 +7,70 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur n'est pas sur l'année couran
 <template>
 <template>
   <!-- TODO : fonctionnement à valider -->
   <!-- TODO : fonctionnement à valider -->
   <UiSystemBar v-if="show" class="theme-warning flex-column">
   <UiSystemBar v-if="show" class="theme-warning flex-column">
-    {{$t('not_current_year')}}
+    {{ $t('not_current_year') }}
 
 
-    <a @click="resetYear" class="text-decoration-none on-warning" style="cursor: pointer;">
+    <a
+      @click="resetYear"
+      class="text-decoration-none on-warning"
+      style="cursor: pointer"
+    >
       <strong class="pl-2 text-neutral-strong">
       <strong class="pl-2 text-neutral-strong">
-        {{$t('not_current_year_reset')}}
+        {{ $t('not_current_year_reset') }}
       </strong>
       </strong>
     </a>
     </a>
   </UiSystemBar>
   </UiSystemBar>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {useAccessProfileStore} from "~/stores/accessProfile";
-  import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-  import type {ComputedRef} from "@vue/reactivity";
-  import {useFormStore} from "~/stores/form";
-  import Access from "~/models/Access/Access";
-  import {usePageStore} from "~/stores/page";
-  import {useEntityManager} from "~/composables/data/useEntityManager";
-  import {useRefreshProfile} from "~/composables/data/useRefreshProfile";
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
+import type { ComputedRef } from '@vue/reactivity'
+import { useFormStore } from '~/stores/form'
+import Access from '~/models/Access/Access'
+import { usePageStore } from '~/stores/page'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
 
 
-  const { em } = useEntityManager()
-  const accessProfile = useAccessProfileStore()
-  const organizationProfile = useOrganizationProfileStore()
-  const { setDirty } = useFormStore()
-  const pageStore = usePageStore()
-  const { refreshProfile } = useRefreshProfile()
+const { em } = useEntityManager()
+const accessProfile = useAccessProfileStore()
+const organizationProfile = useOrganizationProfileStore()
+const { setDirty } = useFormStore()
+const pageStore = usePageStore()
+const { refreshProfile } = useRefreshProfile()
 
 
-  const show: ComputedRef<boolean> = computed(() => {
-    return (
-        accessProfile.historical.past || accessProfile.historical.future ||
-        (accessProfile.historical.dateStart && accessProfile.historical.dateStart.length > 0) ||
-        (accessProfile.historical.dateEnd && accessProfile.historical.dateEnd.length > 0) ||
-        accessProfile.activityYear !== organizationProfile.currentActivityYear
-    )
-  })
+const show: ComputedRef<boolean> = computed(() => {
+  return (
+    accessProfile.historical.past ||
+    accessProfile.historical.future ||
+    (accessProfile.historical.dateStart &&
+      accessProfile.historical.dateStart.length > 0) ||
+    (accessProfile.historical.dateEnd &&
+      accessProfile.historical.dateEnd.length > 0) ||
+    accessProfile.activityYear !== organizationProfile.currentActivityYear
+  )
+})
 
 
-  const resetYear = async () => {
-      const defaultValues = {
-        historical: {
-            "future": false,
-            "past": false,
-            "present": true,
-        },
-        activityYear: organizationProfile.currentActivityYear
-      }
+const resetYear = async () => {
+  const defaultValues = {
+    historical: {
+      future: false,
+      past: false,
+      present: true,
+    },
+    activityYear: organizationProfile.currentActivityYear,
+  }
 
 
-    // Il faut ajouter un patch sur le profile ici
-    setDirty(false)
+  // Il faut ajouter un patch sur le profile ici
+  setDirty(false)
 
 
-    pageStore.loading = true
-    await em.patch(Access, accessProfile.currentAccessId, defaultValues)
-    if (process.server) {
-        // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
-      await refreshProfile()
-    }
-    window.location.reload()
+  pageStore.loading = true
+  await em.patch(Access, accessProfile.currentAccessId, defaultValues)
+  if (process.server) {
+    // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
+    await refreshProfile()
   }
   }
+  window.location.reload()
+}
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 10 - 10
components/Layout/BannerTop.vue

@@ -18,14 +18,14 @@ Troisième bandeau en partant du haut, contenant entre autre le numéro SIRET de
 </template>
 </template>
 
 
 <style scoped>
 <style scoped>
-  .bannerTopForm{
-    min-height: 100px;
-    margin-top: 10px !important;
-    margin-bottom: 10px !important;
-  }
-  .bannerTopForm > .col{
-    min-height: 100px;
-    padding: 10px;
-    padding-left: 24px;
-  }
+.bannerTopForm {
+  min-height: 100px;
+  margin-top: 10px !important;
+  margin-bottom: 10px !important;
+}
+.bannerTopForm > .col {
+  min-height: 100px;
+  padding: 10px;
+  padding-left: 24px;
+}
 </style>
 </style>

+ 3 - 3
components/Layout/Container.vue

@@ -12,7 +12,7 @@
 </template>
 </template>
 
 
 <style scoped>
 <style scoped>
-  .container{
-    padding-top: 0;
-  }
+.container {
+  padding-top: 0;
+}
 </style>
 </style>

+ 39 - 33
components/Layout/Dialog.vue

@@ -7,7 +7,12 @@
     :content-class="contentClass"
     :content-class="contentClass"
   >
   >
     <v-card class="d-flex flex-row">
     <v-card class="d-flex flex-row">
-      <div :class="'dialog-type flex-column justify-center d-none d-sm-flex theme-' + theme">
+      <div
+        :class="
+          'dialog-type flex-column justify-center d-none d-sm-flex theme-' +
+          theme
+        "
+      >
         <h3 class="d-flex">
         <h3 class="d-flex">
           <slot name="dialogType" />
           <slot name="dialogType" />
         </h3>
         </h3>
@@ -34,15 +39,14 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
 const props = defineProps({
 const props = defineProps({
   show: {
   show: {
     type: [Boolean, Object],
     type: [Boolean, Object],
-    required: true
+    required: true,
   },
   },
   contentClass: {
   contentClass: {
     type: String,
     type: String,
-    required: false
+    required: false,
   },
   },
   theme: {
   theme: {
     type: String,
     type: String,
@@ -52,50 +56,52 @@ const props = defineProps({
   maxWidth: {
   maxWidth: {
     type: [Number, String],
     type: [Number, String],
     required: false,
     required: false,
-    default: 800
-  }
+    default: 800,
+  },
 })
 })
 
 
 // @ts-ignore  -> just to avoid the error with the prop's type of v-dialog
 // @ts-ignore  -> just to avoid the error with the prop's type of v-dialog
 const _show = computed(() => props.show) as boolean
 const _show = computed(() => props.show) as boolean
-
-
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-  .dialog-title {
-    padding-left: 40px;
+.dialog-title {
+  padding-left: 40px;
+  font-weight: normal;
+}
+
+.dialog-type {
+  width: 60px;
+  min-width: 60px;
+  max-width: 60px;
+  min-height: 120px;
+  padding: 25px 10px;
+
+  h3 {
+    font-size: 25px;
     font-weight: normal;
     font-weight: normal;
+    writing-mode: vertical-lr;
+    transform: rotate(-180deg);
   }
   }
+}
 
 
-  .dialog-type {
-    width: 60px;
-    min-width: 60px;
-    max-width: 60px;
-    min-height: 120px;
-    padding: 25px 10px;
+.dialog-container {
+  overflow-x: scroll;
+}
 
 
-   h3 {
-     font-size: 25px;
-     font-weight: normal;
-     writing-mode: vertical-lr;
-     transform: rotate(-180deg);
-    }
-  }
-
-  .dialog-text-container {
-    max-height: 70vh;
-    overflow: auto;
-  }
+.dialog-text-container {
+  max-height: 70vh;
+  overflow: auto;
+}
 
 
-  .modal-level-alert {
-    .dialog-type{
-      background: rgb(var(--v-theme-danger, #f56954));
-    }
+.modal-level-alert {
+  .dialog-type {
+    background: rgb(var(--v-theme-danger, #f56954));
   }
   }
+}
 
 
   .modal-level-warning {
   .modal-level-warning {
-    .dialog-type{
+    .dialog-type {
       background: rgb(var(--v-theme-warning, #f39c12));
       background: rgb(var(--v-theme-warning, #f39c12));
     }
     }
   }
   }

+ 45 - 44
components/Layout/Header.vue

@@ -4,22 +4,15 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
 -->
 -->
 
 
 <template>
 <template>
-  <v-app-bar
-      order="0"
-      density="compact"
-      class="theme-primary"
-  >
+  <v-app-bar order="0" density="compact" class="theme-primary">
     <template #prepend>
     <template #prepend>
       <v-app-bar-nav-icon
       <v-app-bar-nav-icon
-          v-if="hasMainMenu && layoutStore.name !== 'parameters'"
-          :icon="isMainMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
-          @click="toggleMainMenu"
+        v-if="hasMainMenu && layoutStore.name !== 'parameters'"
+        :icon="isMainMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
+        @click="toggleMainMenu"
       />
       />
       <div v-else-if="hasParametersMenu && layoutStore.name === 'parameters'">
       <div v-else-if="hasParametersMenu && layoutStore.name === 'parameters'">
-        <v-app-bar-nav-icon
-          v-if="mdAndUp"
-          icon="fa fa-gear"
-        />
+        <v-app-bar-nav-icon v-if="mdAndUp" icon="fa fa-gear" />
         <v-app-bar-nav-icon
         <v-app-bar-nav-icon
           v-else
           v-else
           :icon="isParametersMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
           :icon="isParametersMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
@@ -28,11 +21,15 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
       </div>
       </div>
     </template>
     </template>
 
 
-    <v-toolbar-title v-if="mdAndUp" v-text="title"/>
+    <v-toolbar-title v-if="mdAndUp" v-text="title" />
 
 
-    <LayoutThemeSwitcher v-if="false" /> <!-- En attente validation PO -->
+    <LayoutThemeSwitcher v-if="false" />
+    <!-- En attente validation PO -->
 
 
-    <LayoutHeaderUniversalCreationCreateButton v-if="showUniversalButton" class="mr-3" />
+    <LayoutHeaderUniversalCreationCreateButton
+      v-if="showUniversalButton"
+      class="mr-3"
+    />
 
 
     <LayoutHeaderHomeBtn v-if="smAndUp" />
     <LayoutHeaderHomeBtn v-if="smAndUp" />
 
 
@@ -49,29 +46,34 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
     <LayoutHeaderMenu name="Account" color="on-primary" icon="fas fa-sun" />
     <LayoutHeaderMenu name="Account" color="on-primary" icon="fas fa-sun" />
 
 
     <a
     <a
-        :href="runtimeConfig.supportUrl"
-        class="text-body px-3 py-4 ml-2 theme-secondary text-decoration-none h-100"
-        target="_blank"
+      :href="runtimeConfig.supportUrl"
+      class="text-body px-3 py-4 ml-2 theme-secondary text-decoration-none h-100"
+      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 icon="fas fa-question-circle" class="d-sm-flex d-md-none" color="on-secondary" />
+      <v-icon
+        icon="fas fa-question-circle"
+        class="d-sm-flex d-md-none"
+        color="on-secondary"
+      />
     </a>
     </a>
   </v-app-bar>
   </v-app-bar>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {computed} from "@vue/reactivity";
-import type {ComputedRef} from "@vue/reactivity";
-import {useMenu} from "~/composables/layout/useMenu";
-import {useAbility} from "@casl/vue";
-import {useDisplay} from 'vuetify'
-import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import {useLayoutStore} from "~/stores/layout";
+import { computed } from '@vue/reactivity'
+import type { ComputedRef } from '@vue/reactivity'
+import { useMenu } from '~/composables/layout/useMenu'
+import { useAbility } from '@casl/vue'
+import { useDisplay } from 'vuetify'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
+import { useLayoutStore } from '~/stores/layout'
 
 
 const organizationProfile = useOrganizationProfileStore()
 const organizationProfile = useOrganizationProfileStore()
 const runtimeConfig = useRuntimeConfig()
 const runtimeConfig = useRuntimeConfig()
-const title: ComputedRef<string> = computed(() => organizationProfile.name ?? 'Opentalent')
+const title: ComputedRef<string> = computed(
+  () => organizationProfile.name ?? 'Opentalent',
+)
 
 
 const { hasMenu, isMenuOpened, toggleMenu } = useMenu()
 const { hasMenu, isMenuOpened, toggleMenu } = useMenu()
 
 
@@ -87,25 +89,24 @@ const toggleParametersMenu = () => toggleMenu('Parameters')
 
 
 const ability = useAbility()
 const ability = useAbility()
 const showUniversalButton =
 const showUniversalButton =
-    ability.can('manage', 'users')
-    || ability.can('manage', 'courses')
-    || ability.can('manage', 'examens')
-    || ability.can('manage', 'educationalprojects')
-    || ability.can('manage', 'events')
-    || ability.can('manage', 'emails')
-    || ability.can('manage', 'mails')
-    || ability.can('manage', 'texto')
-    || ability.can('display', 'message_send_page')
-    || ability.can('manage', 'equipments')
+  ability.can('manage', 'users') ||
+  ability.can('manage', 'courses') ||
+  ability.can('manage', 'examens') ||
+  ability.can('manage', 'educationalprojects') ||
+  ability.can('manage', 'events') ||
+  ability.can('manage', 'emails') ||
+  ability.can('manage', 'mails') ||
+  ability.can('manage', 'texto') ||
+  ability.can('display', 'message_send_page') ||
+  ability.can('manage', 'equipments')
 
 
 const layoutStore = useLayoutStore()
 const layoutStore = useLayoutStore()
-
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-  .help {
-    padding: 14px 14px 13px;
-    font-size: 14px;
-    text-decoration: none;
-  }
+.help {
+  padding: 14px 14px 13px;
+  font-size: 14px;
+  text-decoration: none;
+}
 </style>
 </style>

+ 11 - 13
components/Layout/Header/HomeBtn.vue

@@ -1,26 +1,24 @@
 <template>
 <template>
   <div>
   <div>
     <v-btn
     <v-btn
-        ref="btn"
-        icon="fas fa-home"
-        size="small"
-        :href="homeUrl"
-        class="on-primary"
+      ref="btn"
+      icon="fas fa-home"
+      size="small"
+      :href="homeUrl"
+      class="on-primary"
     />
     />
     <v-tooltip :activator="btn" :text="$t('welcome')" location="bottom" />
     <v-tooltip :activator="btn" :text="$t('welcome')" location="bottom" />
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {ref} from "@vue/reactivity";
-  import {useDisplay} from "vuetify";
-  import {useHomeUrl} from "~/composables/utils/useHomeUrl";
+import { ref } from '@vue/reactivity'
+import { useDisplay } from 'vuetify'
+import { useHomeUrl } from '~/composables/utils/useHomeUrl'
 
 
-  const { homeUrl } = useHomeUrl()
+const { homeUrl } = useHomeUrl()
 
 
-  const btn = ref(null);
+const btn = ref(null)
 </script>
 </script>
 
 
-<style scoped>
-
-</style>
+<style scoped></style>

+ 57 - 57
components/Layout/Header/Menu.vue

@@ -5,79 +5,79 @@ header principal (configuration, paramètres du compte...)
 
 
 <template>
 <template>
   <div v-if="displayMenu">
   <div v-if="displayMenu">
-
-    <v-btn
-        ref="btn"
-        icon
-        size="small"
-        class="ml-2"
-    >
+    <v-btn ref="btn" icon size="small" class="ml-2">
       <v-avatar
       <v-avatar
-          v-if="menu.icon.avatarId || menu.icon.avatarByDefault"
-          size="30"
+        v-if="menu.icon.avatarId || menu.icon.avatarByDefault"
+        size="30"
       >
       >
         <UiImage
         <UiImage
-            :imageId="menu.icon.avatarId"
-            :defaultImage="menu.icon.avatarByDefault"
-            :width="30"
+          :imageId="menu.icon.avatarId"
+          :defaultImage="menu.icon.avatarByDefault"
+          :width="30"
         />
         />
       </v-avatar>
       </v-avatar>
 
 
-      <v-icon
-          v-else
-          :icon="menu.icon.name"
-          class="on-primary"
-      />
+      <v-icon v-else :icon="menu.icon.name" class="on-primary" />
     </v-btn>
     </v-btn>
 
 
-    <v-tooltip
-        :activator="btn"
-        :text="$t(menu.label)"
-        location="bottom"
-    />
+    <v-tooltip :activator="btn" :text="$t(menu.label)" location="bottom" />
 
 
     <v-menu
     <v-menu
-        :activator="btn"
-        :model-value="isOpened()"
-        @update:modelValue="onStateUpdated"
+      :activator="btn"
+      :model-value="isOpened()"
+      @update:modelValue="onStateUpdated"
     >
     >
       <v-card>
       <v-card>
         <v-card-title class="theme-neutral text-body-2 font-weight-bold">
         <v-card-title class="theme-neutral text-body-2 font-weight-bold">
-          {{$t(menu.label)}}
+          {{ $t(menu.label) }}
         </v-card-title>
         </v-card-title>
 
 
         <v-card-text class="ma-0 pa-0 header-menu">
         <v-card-text class="ma-0 pa-0 header-menu">
           <v-list density="compact" :subheader="true">
           <v-list density="compact" :subheader="true">
             <template v-for="(child, index) in menu.children" :key="index">
             <template v-for="(child, index) in menu.children" :key="index">
               <v-list-item
               <v-list-item
-                  :id="child.label"
-                  :href="!isInternalLink(child) ? child.to : undefined"
-                  :to="isInternalLink(child) ? child.to : undefined"
+                :id="child.label"
+                :href="!isInternalLink(child) ? child.to : undefined"
+                :to="isInternalLink(child) ? child.to : undefined"
               >
               >
                 <span v-if="child.icon" class="pr-2 d-flex align-center">
                 <span v-if="child.icon" class="pr-2 d-flex align-center">
-                  <v-avatar v-if="menu.icon.avatarId || child.icon.avatarByDefault" size="30" >
-                    <UiImage :imageId="child.icon.avatarId" :defaultImage="child.icon.avatarByDefault" :width="30" />
+                  <v-avatar
+                    v-if="menu.icon.avatarId || child.icon.avatarByDefault"
+                    size="30"
+                  >
+                    <UiImage
+                      :imageId="child.icon.avatarId"
+                      :defaultImage="child.icon.avatarByDefault"
+                      :width="30"
+                    />
                   </v-avatar>
                   </v-avatar>
                   <v-icon v-else class="on-primary" size="small">
                   <v-icon v-else class="on-primary" size="small">
                     {{ child.icon.name }}
                     {{ child.icon.name }}
                   </v-icon>
                   </v-icon>
                 </span>
                 </span>
 
 
-                <span>{{ translateLabel ? $t(child.label) : child.label }}</span>
+                <span>{{
+                  translateLabel ? $t(child.label) : child.label
+                }}</span>
               </v-list-item>
               </v-list-item>
-
             </template>
             </template>
           </v-list>
           </v-list>
         </v-card-text>
         </v-card-text>
 
 
-        <v-card-actions v-if="menu.actions.length > 0" class="ma-0 pa-0 theme-primary">
+        <v-card-actions
+          v-if="menu.actions.length > 0"
+          class="ma-0 pa-0 theme-primary"
+        >
           <template v-for="(action, index) in menu.actions" :key="index">
           <template v-for="(action, index) in menu.actions" :key="index">
             <v-list-item
             <v-list-item
-                :id="action.label"
-                :href="!isInternalLink(action) ? action.to : undefined"
-                :to="isInternalLink(action) ? action.to : undefined"
+              :id="action.label"
+              :href="!isInternalLink(action) ? action.to : undefined"
+              :to="isInternalLink(action) ? action.to : undefined"
             >
             >
-              <v-list-item-title class="text-body-2" v-text="$t(action.label)"/>
+              <v-list-item-title
+                class="text-body-2"
+                v-text="$t(action.label)"
+              />
             </v-list-item>
             </v-list-item>
           </template>
           </template>
         </v-card-actions>
         </v-card-actions>
@@ -87,22 +87,23 @@ header principal (configuration, paramètres du compte...)
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useMenu} from "~/composables/layout/useMenu";
-import {computed, ref} from "@vue/reactivity";
+import { useMenu } from '~/composables/layout/useMenu'
+import { computed, ref } from '@vue/reactivity'
 
 
 const props = defineProps({
 const props = defineProps({
   name: {
   name: {
     type: String,
     type: String,
-    required: true
+    required: true,
   },
   },
   translateLabel: {
   translateLabel: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: true
-  }
+    default: true,
+  },
 })
 })
 
 
-const { getMenu, isInternalLink, hasMenu, setMenuState, isMenuOpened } = useMenu()
+const { getMenu, isInternalLink, hasMenu, setMenuState, isMenuOpened } =
+  useMenu()
 
 
 const menu = getMenu(props.name)
 const menu = getMenu(props.name)
 const displayMenu = computed(() => hasMenu(props.name))
 const displayMenu = computed(() => hasMenu(props.name))
@@ -113,23 +114,22 @@ const onStateUpdated = (e: any) => {
 }
 }
 
 
 const btn = ref(null)
 const btn = ref(null)
-
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  :deep(.v-btn .v-icon) {
-    font-size: 1rem !important;
-  }
+:deep(.v-btn .v-icon) {
+  font-size: 1rem !important;
+}
 
 
-  .v-list {
-    padding: 0;
-  }
+.v-list {
+  padding: 0;
+}
 
 
-  .v-list-item {
-    width: 100%;
-  }
+.v-list-item {
+  width: 100%;
+}
 
 
-  .header-menu .v-list .v-list-item:last-child {
-    border-bottom: none;
-  }
+.header-menu .v-list .v-list-item:last-child {
+  border-bottom: none;
+}
 </style>
 </style>

+ 90 - 85
components/Layout/Header/Notification.vue

@@ -1,19 +1,13 @@
 <template>
 <template>
-  <v-btn
-      ref="btn"
-      icon
-      size="small"
-      class="ml-2"
-  >
+  <v-btn ref="btn" icon size="small" class="ml-2">
     <v-badge
     <v-badge
-        color="warning"
-        offset-x="-4"
-        offset-y="17"
-        :model-value="unreadNotification.length > 0"
-        :content="unreadNotification.length">
-      <v-icon class="on-primary">
-        fa fa-bell
-      </v-icon>
+      color="warning"
+      offset-x="-4"
+      offset-y="17"
+      :model-value="unreadNotification.length > 0"
+      :content="unreadNotification.length"
+    >
+      <v-icon class="on-primary"> fa fa-bell </v-icon>
     </v-badge>
     </v-badge>
   </v-btn>
   </v-btn>
 
 
@@ -22,10 +16,10 @@
   </v-tooltip>
   </v-tooltip>
 
 
   <v-menu
   <v-menu
-      v-if="btn !== null"
-      :activator="btn"
-      v-model="isOpen"
-      location="bottom left"
+    v-if="btn !== null"
+    :activator="btn"
+    v-model="isOpen"
+    location="bottom left"
   >
   >
     <v-card max-width="400">
     <v-card max-width="400">
       <v-card-title class="bg-neutral text-body-2 font-weight-bold">
       <v-card-title class="bg-neutral text-body-2 font-weight-bold">
@@ -35,21 +29,23 @@
       <v-card-text class="ma-0 pa-0 header-menu">
       <v-card-text class="ma-0 pa-0 header-menu">
         <v-list density="compact" :subheader="true" class="pa-0">
         <v-list density="compact" :subheader="true" class="pa-0">
           <v-list-item
           <v-list-item
-              v-for="(notification, index) in notifications"
-              :key="index"
-              :class="'list_item py-3' + `${notification.notificationUsers.length === 0 ? ' unread' : ''}`"
+            v-for="(notification, index) in notifications"
+            :key="index"
+            :class="
+              'list_item py-3' +
+              `${notification.notificationUsers.length === 0 ? ' unread' : ''}`
+            "
           >
           >
             <span class="">{{ getMessage(notification) }}</span>
             <span class="">{{ getMessage(notification) }}</span>
 
 
             <template #append>
             <template #append>
               <v-icon
               <v-icon
-                  v-if="notification.link"
-                  icon="mdi:mdi-download"
-                  @click="download(notification.link)"
-                  class="pt-4"
+                v-if="notification.link"
+                icon="mdi:mdi-download"
+                @click="download(notification.link)"
+                class="pt-4"
               />
               />
             </template>
             </template>
-
           </v-list-item>
           </v-list-item>
 
 
           <v-divider></v-divider>
           <v-divider></v-divider>
@@ -58,19 +54,14 @@
           <span v-intersect="onLastNotificationIntersect" />
           <span v-intersect="onLastNotificationIntersect" />
 
 
           <v-row
           <v-row
-              v-if="pending"
-              class="fill-height mt-3 mb-3"
-              align="center"
-              justify="center"
+            v-if="pending"
+            class="fill-height mt-3 mb-3"
+            align="center"
+            justify="center"
           >
           >
-            <v-progress-circular
-                indeterminate
-                color="neutral"
-            />
+            <v-progress-circular indeterminate color="neutral" />
           </v-row>
           </v-row>
-
         </v-list>
         </v-list>
-
       </v-card-text>
       </v-card-text>
 
 
       <v-card-actions class="ma-0 pa-0">
       <v-card-actions class="ma-0 pa-0">
@@ -79,11 +70,11 @@
           :href="notificationUrl"
           :href="notificationUrl"
           router
           router
           class="theme-primary"
           class="theme-primary"
-          style="width: 100%; height: 52px;"
+          style="width: 100%; height: 52px"
         >
         >
           <v-list-item-title
           <v-list-item-title
-              class="text-body-2"
-              v-text="$t('all_notification')"
+            class="text-body-2"
+            v-text="$t('all_notification')"
           />
           />
         </v-list-item>
         </v-list-item>
       </v-card-actions>
       </v-card-actions>
@@ -92,18 +83,18 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {NOTIFICATION_TYPE} from "~/types/enum/enums";
-import Notification from "~/models/Core/Notification";
-import NotificationUsers from "~/models/Core/NotificationUsers";
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import {computed, ref} from "@vue/reactivity";
-import type {ComputedRef, Ref} from "@vue/reactivity";
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import type {AnyJson, Pagination} from "~/types/data";
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import UrlUtils from "~/services/utils/urlUtils";
-import {useRepo} from "pinia-orm";
-import NotificationRepository from "~/stores/repositories/NotificationRepository";
+import { NOTIFICATION_TYPE } from '~/types/enum/enums'
+import Notification from '~/models/Core/Notification'
+import NotificationUsers from '~/models/Core/NotificationUsers'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import { computed, ref } from '@vue/reactivity'
+import type { ComputedRef, Ref } from '@vue/reactivity'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import type { AnyJson, Pagination } from '~/types/data'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import UrlUtils from '~/services/utils/urlUtils'
+import { useRepo } from 'pinia-orm'
+import NotificationRepository from '~/stores/repositories/NotificationRepository'
 
 
 const accessProfileStore = useAccessProfileStore()
 const accessProfileStore = useAccessProfileStore()
 
 
@@ -121,10 +112,14 @@ const { fetchCollection } = useEntityFetch()
 const notificationRepo = useRepo(NotificationRepository)
 const notificationRepo = useRepo(NotificationRepository)
 
 
 const query: ComputedRef<AnyJson> = computed(() => {
 const query: ComputedRef<AnyJson> = computed(() => {
-  return { 'page': page.value }
+  return { page: page.value }
 })
 })
 
 
-let { data: collection, pending, refresh } = await fetchCollection(Notification, null, query)
+let {
+  data: collection,
+  pending,
+  refresh,
+} = await fetchCollection(Notification, null, query)
 
 
 /**
 /**
  * On récupère les Notifications via le store (sans ça, les mises à jour SSE ne seront pas prises en compte)
  * On récupère les Notifications via le store (sans ça, les mises à jour SSE ne seront pas prises en compte)
@@ -144,10 +139,15 @@ const unreadNotification: ComputedRef<Array<Notification>> = computed(() => {
  * Les metadata dépendront de la dernière valeur du GET lancé
  * Les metadata dépendront de la dernière valeur du GET lancé
  */
  */
 const pagination: ComputedRef<Pagination> = computed(() => {
 const pagination: ComputedRef<Pagination> = computed(() => {
-  return (!pending.value && collection.value !== null) ? collection.value.pagination : {}
+  return !pending.value && collection.value !== null
+    ? collection.value.pagination
+    : {}
 })
 })
 
 
-const notificationUrl = UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#/notifications/list/')
+const notificationUrl = UrlUtils.join(
+  runtimeConfig.baseUrlAdminLegacy,
+  '#/notifications/list/',
+)
 
 
 /**
 /**
  * L'utilisateur a fait défiler le menu jusqu'à la dernière notification affichée
  * L'utilisateur a fait défiler le menu jusqu'à la dernière notification affichée
@@ -165,10 +165,10 @@ const onLastNotificationIntersect = (isIntersecting: boolean) => {
  */
  */
 const update = async () => {
 const update = async () => {
   if (
   if (
-      !pending.value &&
-      pagination.value &&
-      pagination.value.next &&
-      pagination.value.next > 0
+    !pending.value &&
+    pagination.value &&
+    pagination.value.next &&
+    pagination.value.next > 0
   ) {
   ) {
     pending.value = true
     pending.value = true
     page.value = pagination.value.next
     page.value = pagination.value.next
@@ -185,20 +185,27 @@ const update = async () => {
  * @param notification
  * @param notification
  */
  */
 const getMessage = (notification: Notification) => {
 const getMessage = (notification: Notification) => {
-  switch (notification.type){
-    case NOTIFICATION_TYPE.FILE :
-      return `${i18n.t('your_file')} ${notification.message?.fileName} ${i18n.t('is_ready_to_be_downloaded')}`
+  switch (notification.type) {
+    case NOTIFICATION_TYPE.FILE:
+      return `${i18n.t('your_file')} ${notification.message?.fileName} ${i18n.t(
+        'is_ready_to_be_downloaded',
+      )}`
 
 
     case NOTIFICATION_TYPE.MESSAGE:
     case NOTIFICATION_TYPE.MESSAGE:
       if (notification.message?.action)
       if (notification.message?.action)
-        return `${i18n.t('your_message')} ${notification.message?.fileName} ${i18n.t('is_ready_to_be')} ${notification.message.action}`
+        return `${i18n.t('your_message')} ${notification.message
+          ?.fileName} ${i18n.t('is_ready_to_be')} ${
+          notification.message.action
+        }`
 
 
-      return `${i18n.t('your_message')} ${notification.message?.about ?? ''} ${i18n.t('has_been_sent')} `
+      return `${i18n.t('your_message')} ${
+        notification.message?.about ?? ''
+      } ${i18n.t('has_been_sent')} `
 
 
-    case NOTIFICATION_TYPE.SYSTEM :
+    case NOTIFICATION_TYPE.SYSTEM:
       if (notification.message?.about)
       if (notification.message?.about)
         return `${i18n.t(notification.message.about)}`
         return `${i18n.t(notification.message.about)}`
-      break;
+      break
 
 
     default:
     default:
       return i18n.t(notification.name)
       return i18n.t(notification.name)
@@ -209,7 +216,7 @@ const getMessage = (notification: Notification) => {
  * Dès la fermeture du menu, on indique que les notifications non lues, le sont.
  * Dès la fermeture du menu, on indique que les notifications non lues, le sont.
  */
  */
 const unwatch = watch(isOpen, (newValue, oldValue) => {
 const unwatch = watch(isOpen, (newValue, oldValue) => {
-  if (!newValue){
+  if (!newValue) {
     markNotificationsAsRead()
     markNotificationsAsRead()
   }
   }
 })
 })
@@ -225,9 +232,9 @@ const markNotificationAsRead = (notification: Notification) => {
     throw new Error('Current access id is null')
     throw new Error('Current access id is null')
   }
   }
   const notificationUsers = em.newInstance(NotificationUsers, {
   const notificationUsers = em.newInstance(NotificationUsers, {
-    access:`/api/accesses/${accessProfileStore.currentAccessId}`,
-    notification:`/api/notifications/${notification.id}`,
-    isRead: true
+    access: `/api/accesses/${accessProfileStore.currentAccessId}`,
+    notification: `/api/notifications/${notification.id}`,
+    isRead: true,
   })
   })
 
 
   em.persist(NotificationUsers, notificationUsers)
   em.persist(NotificationUsers, notificationUsers)
@@ -254,29 +261,27 @@ const download = (link: string) => {
   }
   }
   // TODO: passer cette logique dans un service ; tester ; voir si possible de réunir avec composables/utils/useDownloadFile.ts
   // TODO: passer cette logique dans un service ; tester ; voir si possible de réunir avec composables/utils/useDownloadFile.ts
 
 
-  const path: string = link.split('/api')[1];
+  const path: string = link.split('/api')[1]
 
 
   // En switch : https://api.test5.opentalent.fr/api/{accessId}/{switchId}/files/{fileId}/download
   // En switch : https://api.test5.opentalent.fr/api/{accessId}/{switchId}/files/{fileId}/download
   // Sans switch : https://local.api.opentalent.fr/api/{accessId}/files/{fileId}/download
   // Sans switch : https://local.api.opentalent.fr/api/{accessId}/files/{fileId}/download
   const url = UrlUtils.join(
   const url = UrlUtils.join(
-      runtimeConfig.baseUrlLegacy,
-      'api',
-      String(accessProfileStore.id),
-      String(accessProfileStore.switchId || ''),
-      path
+    runtimeConfig.baseUrlLegacy,
+    'api',
+    String(accessProfileStore.id),
+    String(accessProfileStore.switchId || ''),
+    path,
   )
   )
 
 
-  window.open(url);
+  window.open(url)
 }
 }
-
-
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  .list_item{
-    white-space: normal;
-  }
-  .unread{
-    background: rgb(var(--v-theme-neutral-soft, white));
-  }
+.list_item {
+  white-space: normal;
+}
+.unread {
+  background: rgb(var(--v-theme-neutral-soft, white));
+}
 </style>
 </style>

+ 89 - 89
components/Layout/Header/UniversalCreation/Card.vue

@@ -19,15 +19,15 @@
     <v-row :no-gutters="true" style="height: 100px">
     <v-row :no-gutters="true" style="height: 100px">
       <v-col cols="3" class="flex-grow-0 flex-shrink-0 d-flex justify-center">
       <v-col cols="3" class="flex-grow-0 flex-shrink-0 d-flex justify-center">
         <v-icon
         <v-icon
-            :icon="icon"
-            size="50"
-            class="ma-2 pa-2 align-self-center text-neutral-strong"
+          :icon="icon"
+          size="50"
+          class="ma-2 pa-2 align-self-center text-neutral-strong"
         />
         />
       </v-col>
       </v-col>
       <v-col
       <v-col
-          cols="9"
-          align-self="center"
-          class="pl-2 infos-container flex-grow-1 flex-shrink-1"
+        cols="9"
+        align-self="center"
+        class="pl-2 infos-container flex-grow-1 flex-shrink-1"
       >
       >
         <h4 class="text-primary">{{ $t(title) }}</h4>
         <h4 class="text-primary">{{ $t(title) }}</h4>
         <p class="text-neutral-strong">
         <p class="text-neutral-strong">
@@ -39,100 +39,100 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import type {PropType} from "@vue/runtime-core";
-  import {MENU_LINK_TYPE} from "~/types/enum/layout";
-  import {useAdminUrl} from "~/composables/utils/useAdminUrl";
-  import UrlUtils from "~/services/utils/urlUtils";
+import type { PropType } from '@vue/runtime-core'
+import { MENU_LINK_TYPE } from '~/types/enum/layout'
+import { useAdminUrl } from '~/composables/utils/useAdminUrl'
+import UrlUtils from '~/services/utils/urlUtils'
 
 
-  const props = defineProps({
-    /**
-     * Target location in the wizard
-     */
-    to: {
-      type: String,
-      required: false,
-      default: null
-    },
-    /**
-     * Target url
-     */
-    href: {
-      type: String,
-      required: false,
-      default: null
-    },
-    /**
-     * Target url
-     */
-    linkType: {
-      type: Number as PropType<MENU_LINK_TYPE>,
-      required: false,
-      default: MENU_LINK_TYPE.V1
-    },
-    /**
-     * Title displayed on the card
-     */
-    title: {
-      type: String,
-      required: true
-    },
-    /**
-     * Description displayed on the card
-     */
-    textContent: {
-      type: String,
-      required: true
-    },
-    /**
-     * Icon displayed on the card
-     */
-    icon: {
-      type: String,
-      required: true
-    }
-  })
+const props = defineProps({
+  /**
+   * Target location in the wizard
+   */
+  to: {
+    type: String,
+    required: false,
+    default: null,
+  },
+  /**
+   * Target url
+   */
+  href: {
+    type: String,
+    required: false,
+    default: null,
+  },
+  /**
+   * Target url
+   */
+  linkType: {
+    type: Number as PropType<MENU_LINK_TYPE>,
+    required: false,
+    default: MENU_LINK_TYPE.V1,
+  },
+  /**
+   * Title displayed on the card
+   */
+  title: {
+    type: String,
+    required: true,
+  },
+  /**
+   * Description displayed on the card
+   */
+  textContent: {
+    type: String,
+    required: true,
+  },
+  /**
+   * Icon displayed on the card
+   */
+  icon: {
+    type: String,
+    required: true,
+  },
+})
 
 
-  const emit = defineEmits(['click'])
+const emit = defineEmits(['click'])
 
 
-  const { makeAdminUrl } = useAdminUrl()
+const { makeAdminUrl } = useAdminUrl()
 
 
-  let url: string | null = null;
+let url: string | null = null
 
 
-  if (props.href !== null) {
-    switch (props.linkType) {
-      case MENU_LINK_TYPE.V1:
-        url = makeAdminUrl(props.href)
-        break;
-      case MENU_LINK_TYPE.EXTERNAL:
-        url = UrlUtils.prependHttps(props.href)
-        break;
-      default:
-        url = props.href
-    }
+if (props.href !== null) {
+  switch (props.linkType) {
+    case MENU_LINK_TYPE.V1:
+      url = makeAdminUrl(props.href)
+      break
+    case MENU_LINK_TYPE.EXTERNAL:
+      url = UrlUtils.prependHttps(props.href)
+      break
+    default:
+      url = props.href
   }
   }
+}
 
 
-  const onClick = () => {
-    emit('click', props.to, url)
-  }
+const onClick = () => {
+  emit('click', props.to, url)
+}
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-  h4 {
-    font-size: 15px;
-    font-weight: bold;
-    margin-bottom: 6px;
-  }
+h4 {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 6px;
+}
 
 
-  p {
-    font-size: 13px;
-  }
+p {
+  font-size: 13px;
+}
 
 
-  .infos-container {
-    padding: 15px 0;
-  }
+.infos-container {
+  padding: 15px 0;
+}
 
 
-  .v-card:hover {
-    cursor: pointer;
-    background: rgb(var(--v-theme-primary-alt));
-  }
+.v-card:hover {
+  cursor: pointer;
+  background: rgb(var(--v-theme-primary-alt));
+}
 </style>
 </style>

+ 153 - 137
components/Layout/Header/UniversalCreation/CreateButton.vue

@@ -5,56 +5,74 @@
 <template>
 <template>
   <main>
   <main>
     <v-btn
     <v-btn
-        v-if="asIcon"
-        :elevation="0"
-        class="theme-primary"
-        :icon="true"
-        size="small"
-        @click="show"
+      v-if="asIcon"
+      :elevation="0"
+      class="theme-primary"
+      :icon="true"
+      size="small"
+      @click="show"
     >
     >
       <v-icon>fas fa-plus</v-icon>
       <v-icon>fas fa-plus</v-icon>
     </v-btn>
     </v-btn>
 
 
     <v-btn
     <v-btn
-        v-else
-        :elevation="2"
-        height="30"
-        class="theme-x-create-btn"
-        @click="show"
+      v-else
+      :elevation="2"
+      height="30"
+      class="theme-x-create-btn"
+      @click="show"
     >
     >
       <span>{{ $t('create') }}</span>
       <span>{{ $t('create') }}</span>
     </v-btn>
     </v-btn>
 
 
-    <LayoutDialog :show="showCreateDialog" :max-width="850" >
+    <LayoutDialog :show="showCreateDialog" :max-width="850">
       <template #dialogType>{{ $t('creative_assistant') }}</template>
       <template #dialogType>{{ $t('creative_assistant') }}</template>
 
 
       <template #dialogTitle>
       <template #dialogTitle>
-        <span v-if="location === 'home'">{{ $t('what_do_you_want_to_create') }}</span>
-        <span v-else-if="location === 'access'">{{ $t('what_type_of_contact_do_you_want_to_create') }}</span>
-        <span v-else-if="location === 'event'">{{ $t('what_do_you_want_to_add_to_your_planning') }}</span>
-        <span v-else-if="location === 'message'">{{ $t('what_do_you_want_to_send') }}</span>
-        <span v-else-if="location === 'event-params'">{{ $t('which_date_and_which_hour') }}</span>
+        <span v-if="location === 'home'">{{
+          $t('what_do_you_want_to_create')
+        }}</span>
+        <span v-else-if="location === 'access'">{{
+          $t('what_type_of_contact_do_you_want_to_create')
+        }}</span>
+        <span v-else-if="location === 'event'">{{
+          $t('what_do_you_want_to_add_to_your_planning')
+        }}</span>
+        <span v-else-if="location === 'message'">{{
+          $t('what_do_you_want_to_send')
+        }}</span>
+        <span v-else-if="location === 'event-params'">{{
+          $t('which_date_and_which_hour')
+        }}</span>
       </template>
       </template>
 
 
       <template #dialogText>
       <template #dialogText>
-         <LayoutHeaderUniversalCreationGenerateCardsSteps
-             :path="path"
-             @cardClick="onCardClick"
-             @urlUpdate="onUrlUpdate"
-         />
+        <LayoutHeaderUniversalCreationGenerateCardsSteps
+          :path="path"
+          @cardClick="onCardClick"
+          @urlUpdate="onUrlUpdate"
+        />
       </template>
       </template>
 
 
       <template #dialogBtn>
       <template #dialogBtn>
         <div class="text-center">
         <div class="text-center">
-          <v-btn class="theme-neutral-soft" @click="close" >
+          <v-btn class="theme-neutral-soft" @click="close">
             {{ $t('cancel') }}
             {{ $t('cancel') }}
           </v-btn>
           </v-btn>
 
 
-          <v-btn v-if="path.length > 1" class="theme-neutral-soft" @click="goToPrevious" >
+          <v-btn
+            v-if="path.length > 1"
+            class="theme-neutral-soft"
+            @click="goToPrevious"
+          >
             {{ $t('previous_step') }}
             {{ $t('previous_step') }}
           </v-btn>
           </v-btn>
 
 
-          <v-btn v-if="targetUrl !== null && !directRedirectionOngoing" class="theme-primary" @click="validate" >
+          <v-btn
+            v-if="targetUrl !== null && !directRedirectionOngoing"
+            class="theme-primary"
+            @click="validate"
+          >
             {{ $t('validate') }}
             {{ $t('validate') }}
           </v-btn>
           </v-btn>
         </div>
         </div>
@@ -64,122 +82,120 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {ref} from "@vue/reactivity";
-  import type {Ref} from "@vue/reactivity";
-  import {useDisplay} from "vuetify";
-  import type {ComputedRef} from "vue";
-  import {usePageStore} from "~/stores/page";
-
-  const { mdAndDown: asIcon } = useDisplay()
-
-  // Set to true to show the Create dialog
-  const showCreateDialog: Ref<boolean> = ref(false);
-
-  // The succession of menus the user has been through; used to keep track of the navigation
-  const path: Ref<Array<string>> = ref(['home'])
-
-  // The current menu
-  const location: ComputedRef<string> = computed(() => {
-    return path.value.at(-1) ?? 'home'
-  })
-
-  // The current target URL (@see onUrlUpdate())
-  const targetUrl: Ref<string | null> = ref(null)
-
-  // Already redirecting (to avoid the display of the 'validate' button when page has already been redirected and is loading)
-  const directRedirectionOngoing: Ref<boolean> = ref(false)
-
-  /**
-   * Return to the home menu
-   */
-  const reset = () => {
-    path.value = ['home']
-  }
-
-  /**
-   * Go back to the previous step
-   */
-  const goToPrevious = () => {
-    if (path.value.length === 1) {
-      return
-    }
-    path.value.pop()
-  }
-
-  /**
-   * Display the create dialog
-   */
-  const show = () => {
-    reset()
-    showCreateDialog.value = true
+import { ref } from '@vue/reactivity'
+import type { Ref } from '@vue/reactivity'
+import { useDisplay } from 'vuetify'
+import type { ComputedRef } from 'vue'
+import { usePageStore } from '~/stores/page'
+
+const { mdAndDown: asIcon } = useDisplay()
+
+// Set to true to show the Create dialog
+const showCreateDialog: Ref<boolean> = ref(false)
+
+// The succession of menus the user has been through; used to keep track of the navigation
+const path: Ref<Array<string>> = ref(['home'])
+
+// The current menu
+const location: ComputedRef<string> = computed(() => {
+  return path.value.at(-1) ?? 'home'
+})
+
+// The current target URL (@see onUrlUpdate())
+const targetUrl: Ref<string | null> = ref(null)
+
+// Already redirecting (to avoid the display of the 'validate' button when page has already been redirected and is loading)
+const directRedirectionOngoing: Ref<boolean> = ref(false)
+
+/**
+ * Return to the home menu
+ */
+const reset = () => {
+  path.value = ['home']
+}
+
+/**
+ * Go back to the previous step
+ */
+const goToPrevious = () => {
+  if (path.value.length === 1) {
+    return
   }
   }
-
-  const pageStore = usePageStore()
-
-  /**
-   * Redirect the user to the given url
-   * @param url
-   */
-  const redirect = (url: string) => {
-    pageStore.loading = true
-    window.location.href = url
-  }
-
-  /**
-   * Go to the current targetUrl
-   */
-  const validate = () => {
-    if (targetUrl.value === null) {
-      console.warn('No url defined')
-      return
-    }
-    redirect(targetUrl.value)
+  path.value.pop()
+}
+
+/**
+ * Display the create dialog
+ */
+const show = () => {
+  reset()
+  showCreateDialog.value = true
+}
+
+const pageStore = usePageStore()
+
+/**
+ * Redirect the user to the given url
+ * @param url
+ */
+const redirect = (url: string) => {
+  pageStore.loading = true
+  window.location.href = url
+}
+
+/**
+ * Go to the current targetUrl
+ */
+const validate = () => {
+  if (targetUrl.value === null) {
+    console.warn('No url defined')
+    return
   }
   }
-
-  /**
-   * Close the Create dialog
-   */
-  const close = () => {
-    showCreateDialog.value = false
-  }
-
-  /**
-   * A cart has been clicked. The reaction depends on the card's properties.
-   *
-   * @param to  Target location in the wizard
-   * @param href  Target absolute url
-   */
-  const onCardClick = (to: string | null, href: string | null) => {
-    if (to !== null) {
-      // La carte définit une nouvelle destination : on se dirige vers elle.
-      path.value.push(to)
-
-    } else if (href !== null) {
-      // La carte définit une url avec href, et pas de nouvelle destination : on suit directement le lien pour éviter
-      // l'étape de validation devenue inutile.
-      directRedirectionOngoing.value = true
-      redirect(href)
-
-    } else {
-      console.warn('Error: card has no `to` nor `href` defined')
-    }
-  }
-
-  /**
-   * The url has been updated in the GenerateCardsStep component
-   * @param url
-   */
-  const onUrlUpdate = (url: string) => {
-    targetUrl.value = url
+  redirect(targetUrl.value)
+}
+
+/**
+ * Close the Create dialog
+ */
+const close = () => {
+  showCreateDialog.value = false
+}
+
+/**
+ * A cart has been clicked. The reaction depends on the card's properties.
+ *
+ * @param to  Target location in the wizard
+ * @param href  Target absolute url
+ */
+const onCardClick = (to: string | null, href: string | null) => {
+  if (to !== null) {
+    // La carte définit une nouvelle destination : on se dirige vers elle.
+    path.value.push(to)
+  } else if (href !== null) {
+    // La carte définit une url avec href, et pas de nouvelle destination : on suit directement le lien pour éviter
+    // l'étape de validation devenue inutile.
+    directRedirectionOngoing.value = true
+    redirect(href)
+  } else {
+    console.warn('Error: card has no `to` nor `href` defined')
   }
   }
+}
+
+/**
+ * The url has been updated in the GenerateCardsStep component
+ * @param url
+ */
+const onUrlUpdate = (url: string) => {
+  targetUrl.value = url
+}
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  :deep(.v-btn .v-icon) {
-    font-size: 16px !important;
-  }
-  :deep(.v-btn) {
-    text-transform: none !important;
-    font-weight: 600;
-  }
+:deep(.v-btn .v-icon) {
+  font-size: 16px !important;
+}
+:deep(.v-btn) {
+  text-transform: none !important;
+  font-weight: 600;
+}
 </style>
 </style>

+ 72 - 62
components/Layout/Header/UniversalCreation/EventParams.vue

@@ -17,7 +17,10 @@ Event parameters page in the create dialog
     <v-row v-show="eventStart < now" class="anteriorDateWarning mt-0">
     <v-row v-show="eventStart < now" class="anteriorDateWarning mt-0">
       <v-col cols="2" class="pt-1"></v-col>
       <v-col cols="2" class="pt-1"></v-col>
       <v-col cols="9" class="pt-1">
       <v-col cols="9" class="pt-1">
-        <i class="fa fa-circle-info" /> {{ $t('please_note_that_this_reservation_start_on_date_anterior_to_now') }}
+        <i class="fa fa-circle-info" />
+        {{
+          $t('please_note_that_this_reservation_start_on_date_anterior_to_now')
+        }}
       </v-col>
       </v-col>
     </v-row>
     </v-row>
 
 
@@ -36,7 +39,6 @@ Event parameters page in the create dialog
         <UiInputNumber v-model="eventDurationMinutes" class="mx-3" :min="0" />
         <UiInputNumber v-model="eventDurationMinutes" class="mx-3" :min="0" />
         <span>{{ $t('minute(s)') }}</span>
         <span>{{ $t('minute(s)') }}</span>
       </v-col>
       </v-col>
-
     </v-row>
     </v-row>
 
 
     <v-row>
     <v-row>
@@ -52,69 +54,77 @@ Event parameters page in the create dialog
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {ref} from "@vue/reactivity";
-  import type {Ref} from "@vue/reactivity";
-  import {add, format, startOfHour, formatISO} from "date-fns";
-  import type {ComputedRef} from "vue";
-  import DateUtils, {supportedLocales} from "~/services/utils/dateUtils";
-
-  const i18n = useI18n()
-
-  // An event is sent each time the resulting params are updated
-  const emit = defineEmits(['paramsUpdated'])
-
-  // Get the start of the next hour as a default event start
-  const now: Date = new Date()
-  const eventStart: Ref<Date> = ref(startOfHour(add(now, { 'hours': 1 })))
-
-  const eventDurationDays: Ref<number> = ref(0)
-  const eventDurationHours: Ref<number> = ref(1)
-  const eventDurationMinutes: Ref<number> = ref(0)
-
-  // Duration of the events, in minutes
-  const eventDuration: ComputedRef<number> = computed(() => {
-    return (eventDurationDays.value * 24 * 60) + (eventDurationHours.value * 60) + eventDurationMinutes.value
+import { ref } from '@vue/reactivity'
+import type { Ref } from '@vue/reactivity'
+import { add, format, startOfHour, formatISO } from 'date-fns'
+import type { ComputedRef } from 'vue'
+import DateUtils, { supportedLocales } from '~/services/utils/dateUtils'
+
+const i18n = useI18n()
+
+// An event is sent each time the resulting params are updated
+const emit = defineEmits(['paramsUpdated'])
+
+// Get the start of the next hour as a default event start
+const now: Date = new Date()
+const eventStart: Ref<Date> = ref(startOfHour(add(now, { hours: 1 })))
+
+const eventDurationDays: Ref<number> = ref(0)
+const eventDurationHours: Ref<number> = ref(1)
+const eventDurationMinutes: Ref<number> = ref(0)
+
+// Duration of the events, in minutes
+const eventDuration: ComputedRef<number> = computed(() => {
+  return (
+    eventDurationDays.value * 24 * 60 +
+    eventDurationHours.value * 60 +
+    eventDurationMinutes.value
+  )
+})
+
+// Event end
+const eventEnd: ComputedRef<Date> = computed(() =>
+  add(eventStart.value, { minutes: eventDuration.value }),
+)
+
+const fnsLocale = DateUtils.getFnsLocale(i18n.locale.value as supportedLocales)
+const formattedEventEnd: ComputedRef<string> = computed(() => {
+  return format(eventEnd.value, 'EEEE dd MMMM yyyy HH:mm', {
+    locale: fnsLocale,
   })
   })
+})
 
 
-  // Event end
-  const eventEnd: ComputedRef<Date> = computed(() => add(eventStart.value, { 'minutes': eventDuration.value }))
-
-  const fnsLocale = DateUtils.getFnsLocale(i18n.locale.value as supportedLocales)
-  const formattedEventEnd: ComputedRef<string> = computed(() => {
-    return format(eventEnd.value, 'EEEE dd MMMM yyyy HH:mm', {locale: fnsLocale})
-  })
-
-  // Build the event params
-  const params: ComputedRef<{'start': string, 'end': string}> = computed(() => {
-    return {
-      'start': formatISO(eventStart.value),
-      'end': formatISO(eventEnd.value),
-    }
-  })
-
-  // Send an update event as soon as the page is mounted
-  onMounted(() => {
-    emit('paramsUpdated', params.value)
-  })
-
-  // Send an update event every time the params change
-  const unwatch = watch(params, (newParams) => {
-    emit('paramsUpdated', newParams)
-  })
-  onUnmounted(() => {
-    unwatch()
-  })
+// Build the event params
+const params: ComputedRef<{ start: string; end: string }> = computed(() => {
+  return {
+    start: formatISO(eventStart.value),
+    end: formatISO(eventEnd.value),
+  }
+})
+
+// Send an update event as soon as the page is mounted
+onMounted(() => {
+  emit('paramsUpdated', params.value)
+})
+
+// Send an update event every time the params change
+const unwatch = watch(params, (newParams) => {
+  emit('paramsUpdated', newParams)
+})
+onUnmounted(() => {
+  unwatch()
+})
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  .endDate {
-    font-weight: 600;
-    text-transform: capitalize;
-    color: rgb(var(--v-theme-on-neutral));
-  }
-
-  .anteriorDateWarning {
-    color: rgb(var(--v-theme-info));
-    font-weight: 600;
-  }
+.endDate {
+  font-weight: 600;
+  text-transform: capitalize;
+  color: rgb(var(--v-theme-on-neutral));
+}
+
+.anteriorDateWarning {
+  color: rgb(var(--v-theme-info));
+  font-weight: 600;
+}
 </style>
 </style>

+ 190 - 178
components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue

@@ -3,57 +3,67 @@
 -->
 -->
 
 
 <template>
 <template>
-
   <!-- Menu Accueil -->
   <!-- Menu Accueil -->
   <v-container v-if="location === 'home'">
   <v-container v-if="location === 'home'">
     <v-row>
     <v-row>
-
       <!-- Une personne -->
       <!-- Une personne -->
       <v-col cols="6" v-if="ability.can('manage', 'users')">
       <v-col cols="6" v-if="ability.can('manage', 'users')">
-          <LayoutHeaderUniversalCreationCard
-              to="access"
-              title="a_person"
-              text-content="add_new_person_student"
-              icon="fa fa-user"
-              @click="onCardClick"
-          />
+        <LayoutHeaderUniversalCreationCard
+          to="access"
+          title="a_person"
+          text-content="add_new_person_student"
+          icon="fa fa-user"
+          @click="onCardClick"
+        />
       </v-col>
       </v-col>
 
 
       <!-- Un évènement -->
       <!-- Un évènement -->
-      <v-col cols="6" v-if="ability.can('display', 'agenda_page')
-                && (
-                   ability.can('display', 'course_page') ||
-                   ability.can('display', 'exam_page') ||
-                   ability.can('display', 'pedagogics_project_page')
-                )">
+      <v-col
+        cols="6"
+        v-if="
+          ability.can('display', 'agenda_page') &&
+          (ability.can('display', 'course_page') ||
+            ability.can('display', 'exam_page') ||
+            ability.can('display', 'pedagogics_project_page'))
+        "
+      >
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            to="event"
-            title="an_event"
-            text-content="add_an_event_course"
-            icon="fa fa-calendar-alt"
-            @click="onCardClick"
+          to="event"
+          title="an_event"
+          text-content="add_an_event_course"
+          icon="fa fa-calendar-alt"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Autre évènement -->
       <!-- Autre évènement -->
-      <v-col cols="6" v-else-if="ability.can('display', 'agenda_page') && ability.can('manage', 'events')">
+      <v-col
+        cols="6"
+        v-else-if="
+          ability.can('display', 'agenda_page') &&
+          ability.can('manage', 'events')
+        "
+      >
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            to="event-params"
-            title="other_event"
-            text-content="other_event_text_creation_card"
-            icon="far fa-calendar"
-            href="/calendar/create/events"
-            @click="onCardClick"
+          to="event-params"
+          title="other_event"
+          text-content="other_event_text_creation_card"
+          icon="far fa-calendar"
+          href="/calendar/create/events"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Une correspondance -->
       <!-- Une correspondance -->
-      <v-col cols="6" v-if="ability.can('display', 'message_send_page')
-                   && (
-                    ability.can('manage', 'emails') ||
-                    ability.can('manage', 'mails') ||
-                    ability.can('manage', 'texto')
-                  )">
+      <v-col
+        cols="6"
+        v-if="
+          ability.can('display', 'message_send_page') &&
+          (ability.can('manage', 'emails') ||
+            ability.can('manage', 'mails') ||
+            ability.can('manage', 'texto'))
+        "
+      >
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
           to="message"
           to="message"
           title="a_correspondence"
           title="a_correspondence"
@@ -83,99 +93,99 @@
       <!-- Un adhérent -->
       <!-- Un adhérent -->
       <v-col cols="6" v-if="isLaw1901">
       <v-col cols="6" v-if="isLaw1901">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="an_adherent"
-            text-content="adherent_text_creation_card"
-            icon="fa fa-user"
-            href="/universal_creation_person/adherent"
-            @click="onCardClick"
+          title="an_adherent"
+          text-content="adherent_text_creation_card"
+          icon="fa fa-user"
+          href="/universal_creation_person/adherent"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un membre du CA -->
       <!-- Un membre du CA -->
       <v-col cols="6" v-if="isLaw1901">
       <v-col cols="6" v-if="isLaw1901">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="a_ca_member"
-            text-content="ca_member_text_creation_card"
-            icon="fa fa-users"
-            href="/universal_creation_person/ca_member"
-            @click="onCardClick"
+          title="a_ca_member"
+          text-content="ca_member_text_creation_card"
+          icon="fa fa-users"
+          href="/universal_creation_person/ca_member"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un élève -->
       <!-- Un élève -->
       <v-col cols="6">
       <v-col cols="6">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="a_student"
-            text-content="student_text_creation_card"
-            icon="fa fa-user"
-            href="/universal_creation_person/student"
-            @click="onCardClick"
+          title="a_student"
+          text-content="student_text_creation_card"
+          icon="fa fa-user"
+          href="/universal_creation_person/student"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un tuteur -->
       <!-- Un tuteur -->
       <v-col cols="6">
       <v-col cols="6">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="a_guardian"
-            text-content="guardian_text_creation_card"
-            icon="fa fa-female"
-            href="/universal_creation_person/guardian"
-            @click="onCardClick"
+          title="a_guardian"
+          text-content="guardian_text_creation_card"
+          icon="fa fa-female"
+          href="/universal_creation_person/guardian"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un professeur -->
       <!-- Un professeur -->
       <v-col cols="6">
       <v-col cols="6">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="a_teacher"
-            text-content="teacher_text_creation_card"
-            icon="fa fa-graduation-cap"
-            href="/universal_creation_person/teacher"
-            @click="onCardClick"
+          title="a_teacher"
+          text-content="teacher_text_creation_card"
+          icon="fa fa-graduation-cap"
+          href="/universal_creation_person/teacher"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un membre du personnel -->
       <!-- Un membre du personnel -->
       <v-col cols="6">
       <v-col cols="6">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="a_member_of_staff"
-            text-content="personnel_text_creation_card"
-            icon="fa fa-suitcase"
-            href="/universal_creation_person/personnel"
-            @click="onCardClick"
+          title="a_member_of_staff"
+          text-content="personnel_text_creation_card"
+          icon="fa fa-suitcase"
+          href="/universal_creation_person/personnel"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Une entité légale -->
       <!-- Une entité légale -->
       <v-col cols="6">
       <v-col cols="6">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="a_legal_entity"
-            text-content="moral_text_creation_card"
-            icon="fa fa-building"
-            href="/universal_creation_person/company"
-            @click="onCardClick"
+          title="a_legal_entity"
+          text-content="moral_text_creation_card"
+          icon="fa fa-building"
+          href="/universal_creation_person/company"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Une inscription en ligne -->
       <!-- Une inscription en ligne -->
       <v-col cols="6" v-if="hasOnlineRegistrationModule">
       <v-col cols="6" v-if="hasOnlineRegistrationModule">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="online_registration"
-            text-content="online_registration_text_creation_card"
-            icon="fa fa-list-alt"
-            href="/online/registration/new_registration"
-            @click="onCardClick"
+          title="online_registration"
+          text-content="online_registration_text_creation_card"
+          icon="fa fa-list-alt"
+          href="/online/registration/new_registration"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un autre type de contact -->
       <!-- Un autre type de contact -->
       <v-col cols="6">
       <v-col cols="6">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="another_type_of_contact"
-            text-content="other_contact_text_creation_card"
-            icon="fa fa-plus"
-            href="/universal_creation_person/other_contact"
-            @click="onCardClick"
+          title="another_type_of_contact"
+          text-content="other_contact_text_creation_card"
+          icon="fa fa-plus"
+          href="/universal_creation_person/other_contact"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
     </v-row>
     </v-row>
@@ -187,48 +197,48 @@
       <!-- Un cours -->
       <!-- Un cours -->
       <v-col cols="6" v-if="ability.can('display', 'course_page')">
       <v-col cols="6" v-if="ability.can('display', 'course_page')">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            to="event-params"
-            href="/calendar/create/courses"
-            title="course"
-            text-content="course_text_creation_card"
-            icon="fa fa-users"
-            @click="onCardClick"
+          to="event-params"
+          href="/calendar/create/courses"
+          title="course"
+          text-content="course_text_creation_card"
+          icon="fa fa-users"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un examen -->
       <!-- Un examen -->
       <v-col cols="6" v-if="ability.can('display', 'exam_page')">
       <v-col cols="6" v-if="ability.can('display', 'exam_page')">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            to="event-params"
-            href="/calendar/create/examens"
-            title="exam"
-            text-content="exam_text_creation_card"
-            icon="fa fa-graduation-cap"
-            @click="onCardClick"
+          to="event-params"
+          href="/calendar/create/examens"
+          title="exam"
+          text-content="exam_text_creation_card"
+          icon="fa fa-graduation-cap"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un projet pédagogique -->
       <!-- Un projet pédagogique -->
       <v-col cols="6" v-if="ability.can('display', 'pedagogics_project_page')">
       <v-col cols="6" v-if="ability.can('display', 'pedagogics_project_page')">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            to="event-params"
-            href="/calendar/create/educational_projects"
-            title="educational_services"
-            text-content="educational_services_text_creation_card"
-            icon="fa fa-suitcase"
-            @click="onCardClick"
+          to="event-params"
+          href="/calendar/create/educational_projects"
+          title="educational_services"
+          text-content="educational_services_text_creation_card"
+          icon="fa fa-suitcase"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un autre évènement -->
       <!-- Un autre évènement -->
       <v-col cols="6" v-if="ability.can('manage', 'events')">
       <v-col cols="6" v-if="ability.can('manage', 'events')">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            to="event-params"
-            href="/calendar/create/events"
-            title="other_event"
-            text-content="other_event_text_creation_card"
-            icon="far fa-calendar"
-            @click="onCardClick"
+          to="event-params"
+          href="/calendar/create/events"
+          title="other_event"
+          text-content="other_event_text_creation_card"
+          icon="far fa-calendar"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
     </v-row>
     </v-row>
@@ -240,33 +250,33 @@
       <!-- Un email -->
       <!-- Un email -->
       <v-col cols="6" v-if="ability.can('manage', 'emails')">
       <v-col cols="6" v-if="ability.can('manage', 'emails')">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="an_email"
-            text-content="email_text_creation_card"
-            icon="far fa-envelope"
-            href="/list/create/emails"
-            @click="onCardClick"
+          title="an_email"
+          text-content="email_text_creation_card"
+          icon="far fa-envelope"
+          href="/list/create/emails"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un courrier -->
       <!-- Un courrier -->
       <v-col cols="6" v-if="ability.can('manage', 'mails')">
       <v-col cols="6" v-if="ability.can('manage', 'mails')">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="a_letter"
-            text-content="letter_text_creation_card"
-            icon="far fa-file-alt"
-            href="/list/create/mails"
-            @click="onCardClick"
+          title="a_letter"
+          text-content="letter_text_creation_card"
+          icon="far fa-file-alt"
+          href="/list/create/mails"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
 
 
       <!-- Un SMS -->
       <!-- Un SMS -->
       <v-col cols="6" v-if="ability.can('manage', 'texto')">
       <v-col cols="6" v-if="ability.can('manage', 'texto')">
         <LayoutHeaderUniversalCreationCard
         <LayoutHeaderUniversalCreationCard
-            title="a_sms"
-            text-content="sms_text_creation_card"
-            icon="fa fa-mobile-alt"
-            href="/list/create/sms"
-            @click="onCardClick"
+          title="a_sms"
+          text-content="sms_text_creation_card"
+          icon="fa fa-mobile-alt"
+          href="/list/create/sms"
+          @click="onCardClick"
         />
         />
       </v-col>
       </v-col>
     </v-row>
     </v-row>
@@ -274,74 +284,76 @@
 
 
   <!-- Page de pré-paramétrage des évènements -->
   <!-- Page de pré-paramétrage des évènements -->
   <LayoutHeaderUniversalCreationEventParams
   <LayoutHeaderUniversalCreationEventParams
-      v-if="location === 'event-params'"
-      @params-updated="onEventParamsUpdated"
+    v-if="location === 'event-params'"
+    @params-updated="onEventParamsUpdated"
   />
   />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import type {Ref} from "@vue/reactivity";
-  import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-  import {useAbility} from "@casl/vue";
-  import type {ComputedRef} from "vue";
-  import UrlUtils from "~/services/utils/urlUtils";
-
-  const props = defineProps({
-    /**
-     * The path that the user followed troughout the wizard
-     */
-    path: {
-      type: Array<string>,
-      required: true
-    }
-  })
-
-  const location: ComputedRef<string> = computed(() => {
-    return props.path.at(-1) ?? 'home'
-  })
-
-  const ability = useAbility()
-
-  const organizationProfile = useOrganizationProfileStore()
-  const isLaw1901: ComputedRef<boolean> = organizationProfile.isAssociation
-  const hasOnlineRegistrationModule: Ref<boolean> = ref(organizationProfile.hasModule('IEL'))
-
-  const baseUrl: Ref<string | null> = ref(null)
-  const query: Ref<Record<string, string>> = ref({})
-
-  const url: ComputedRef<string | null> = computed(() => {
-    if (baseUrl.value === null) {
-      return null
-    }
-    return UrlUtils.addQuery(baseUrl.value, query.value)
-  })
-
-  const emit = defineEmits(['cardClick', 'urlUpdate'])
+import type { Ref } from '@vue/reactivity'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
+import { useAbility } from '@casl/vue'
+import type { ComputedRef } from 'vue'
+import UrlUtils from '~/services/utils/urlUtils'
 
 
+const props = defineProps({
   /**
   /**
-   * Called when a card is clicked
-   * @param to  Target location in the wizard
-   * @param href  Target absolute url
+   * The path that the user followed troughout the wizard
    */
    */
-  const onCardClick = (to: string | null, href: string | null) => {
-    if (href !== null) {
-      baseUrl.value = href
-    }
-    emit('cardClick', to, url.value)
+  path: {
+    type: Array<string>,
+    required: true,
+  },
+})
+
+const location: ComputedRef<string> = computed(() => {
+  return props.path.at(-1) ?? 'home'
+})
+
+const ability = useAbility()
+
+const organizationProfile = useOrganizationProfileStore()
+const isLaw1901: ComputedRef<boolean> = organizationProfile.isAssociation
+const hasOnlineRegistrationModule: Ref<boolean> = ref(
+  organizationProfile.hasModule('IEL'),
+)
+
+const baseUrl: Ref<string | null> = ref(null)
+const query: Ref<Record<string, string>> = ref({})
+
+const url: ComputedRef<string | null> = computed(() => {
+  if (baseUrl.value === null) {
+    return null
   }
   }
-
-  /**
-   * Called when the event parameters page is updated
-   * @param event
-   */
-  const onEventParamsUpdated = (event: {'start': string, 'end': string}) => {
-    query.value = event
+  return UrlUtils.addQuery(baseUrl.value, query.value)
+})
+
+const emit = defineEmits(['cardClick', 'urlUpdate'])
+
+/**
+ * Called when a card is clicked
+ * @param to  Target location in the wizard
+ * @param href  Target absolute url
+ */
+const onCardClick = (to: string | null, href: string | null) => {
+  if (href !== null) {
+    baseUrl.value = href
   }
   }
-
-  const unwatch = watch(url, (newUrl: string | null) => {
-    emit('urlUpdate', newUrl)
-  })
-  onUnmounted(() => {
-    unwatch()
-  })
+  emit('cardClick', to, url.value)
+}
+
+/**
+ * Called when the event parameters page is updated
+ * @param event
+ */
+const onEventParamsUpdated = (event: { start: string; end: string }) => {
+  query.value = event
+}
+
+const unwatch = watch(url, (newUrl: string | null) => {
+  emit('urlUpdate', newUrl)
+})
+onUnmounted(() => {
+  unwatch()
+})
 </script>
 </script>

+ 15 - 18
components/Layout/LoadingScreen.vue

@@ -2,31 +2,28 @@
 
 
 <template>
 <template>
   <v-overlay
   <v-overlay
-          v-model="pageStore.loading"
-          z-index="9000"
-          persistent
-          class="align-center justify-center"
+    v-model="pageStore.loading"
+    z-index="9000"
+    persistent
+    class="align-center justify-center"
   >
   >
-    <v-progress-circular
-      indeterminate
-      size="64"
-    />
+    <v-progress-circular indeterminate size="64" />
   </v-overlay>
   </v-overlay>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {usePageStore} from "~/stores/page";
+import { usePageStore } from '~/stores/page'
 
 
-  const pageStore = usePageStore()
+const pageStore = usePageStore()
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-  .loading-page {
-    position: fixed;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    z-index: 1001!important;
-  }
+.loading-page {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1001 !important;
+}
 </style>
 </style>

+ 74 - 75
components/Layout/MainMenu.vue

@@ -5,23 +5,18 @@ Prend en paramètre une liste de ItemMenu et les met en forme
 
 
 <template>
 <template>
   <v-navigation-drawer
   <v-navigation-drawer
-      v-model="displayMenu"
-      :rail="isRail"
-      :disable-resize-watcher="true"
-      class="theme-secondary main-menu"
+    v-model="displayMenu"
+    :rail="isRail"
+    :disable-resize-watcher="true"
+    class="theme-secondary main-menu"
   >
   >
     <template #prepend>
     <template #prepend>
       <slot name="title"></slot>
       <slot name="title"></slot>
     </template>
     </template>
 
 
-    <v-list
-        open-strategy="single"
-        active-class="active"
-        class="left-menu"
-    >
+    <v-list open-strategy="single" active-class="active" class="left-menu">
       <!-- TODO: que se passe-t-il si le menu ne comprend qu'un seul MenuItem? -->
       <!-- TODO: que se passe-t-il si le menu ne comprend qu'un seul MenuItem? -->
       <div v-for="(item, i) in items" :key="i">
       <div v-for="(item, i) in items" :key="i">
-
         <!-- Cas 1 : l'item n'a pas d'enfants, c'est un lien (ou le menu est en mode réduit) -->
         <!-- Cas 1 : l'item n'a pas d'enfants, c'est un lien (ou le menu est en mode réduit) -->
         <v-list-item
         <v-list-item
           v-if="!item.children || isRail"
           v-if="!item.children || isRail"
@@ -42,11 +37,11 @@ Prend en paramètre une liste de ItemMenu et les met en forme
         >
         >
           <template #activator="{ props }">
           <template #activator="{ props }">
             <v-list-item
             <v-list-item
-                v-bind="props"
-                :prepend-icon="item.icon.name"
-                :title="$t(item.label)"
-                class="theme-secondary menu-item"
-                height="48px"
+              v-bind="props"
+              :prepend-icon="item.icon.name"
+              :title="$t(item.label)"
+              class="theme-secondary menu-item"
+              height="48px"
             />
             />
           </template>
           </template>
 
 
@@ -69,16 +64,16 @@ Prend en paramètre une liste de ItemMenu et les met en forme
       <slot name="foot"></slot>
       <slot name="foot"></slot>
     </template>
     </template>
   </v-navigation-drawer>
   </v-navigation-drawer>
-
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useMenu} from "~/composables/layout/useMenu";
-import {computed} from "@vue/reactivity";
+import { useMenu } from '~/composables/layout/useMenu'
+import { computed } from '@vue/reactivity'
 import { useDisplay } from 'vuetify'
 import { useDisplay } from 'vuetify'
-import type { MenuGroup, MenuItem } from "~/types/layout";
+import type { MenuGroup, MenuItem } from '~/types/layout'
 
 
-const { getMenu, hasMenu, isInternalLink, setMenuState, isMenuOpened } = useMenu()
+const { getMenu, hasMenu, isInternalLink, setMenuState, isMenuOpened } =
+  useMenu()
 
 
 const { mdAndUp, lgAndUp } = useDisplay()
 const { mdAndUp, lgAndUp } = useDisplay()
 
 
@@ -103,11 +98,16 @@ const displayMenu = computed(() => {
 // En vue md+, fermer le menu le passe simplement en mode rail
 // En vue md+, fermer le menu le passe simplement en mode rail
 // Sinon, le fermer le masque complètement
 // Sinon, le fermer le masque complètement
 const isRail = computed(() => {
 const isRail = computed(() => {
-  return menu !== null && mdAndUp.value && !isOpened.value && !items.some((item) => item.expanded)
+  return (
+    menu !== null &&
+    mdAndUp.value &&
+    !isOpened.value &&
+    !items.some((item) => item.expanded)
+  )
 })
 })
 
 
 const unwatch = watch(lgAndUp, (newValue, oldValue) => {
 const unwatch = watch(lgAndUp, (newValue, oldValue) => {
-// Par défaut si l'écran est trop petit au chargement de la page, le menu doit rester fermé.
+  // Par défaut si l'écran est trop petit au chargement de la page, le menu doit rester fermé.
   if (process.client && menu !== null) {
   if (process.client && menu !== null) {
     setMenuState('Main', lgAndUp.value)
     setMenuState('Main', lgAndUp.value)
   }
   }
@@ -119,68 +119,67 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
+.v-list-item {
+  min-height: 10px !important;
+}
 
 
-  .v-list-item {
-    min-height: 10px !important;
-  }
-
-  :deep(.v-list-item-title), :deep(.v-icon)
-  {
-    font-size: 14px;
-    color: rgb(var(--v-theme-on-secondary));
-  }
-
-  .v-list-item__prepend {
-    margin: 10px 0;
-    margin-right: 10px !important;
-  }
+:deep(.v-list-item-title),
+:deep(.v-icon) {
+  font-size: 14px;
+  color: rgb(var(--v-theme-on-secondary));
+}
 
 
-  .v-application--is-ltr .v-list-group--no-action > .v-list-group__header {
-    margin-left: 0;
-    padding-left: 0;
-  }
-  .v-application--is-ltr .v-list-group--no-action > .v-list-group__items > .v-list-item {
-    padding-left: 30px;
-  }
+.v-list-item__prepend {
+  margin: 10px 0;
+  margin-right: 10px !important;
+}
 
 
-  .v-list-item__content {
-    padding: 8px 0;
-  }
+.v-application--is-ltr .v-list-group--no-action > .v-list-group__header {
+  margin-left: 0;
+  padding-left: 0;
+}
+.v-application--is-ltr
+  .v-list-group--no-action
+  > .v-list-group__items
+  > .v-list-item {
+  padding-left: 30px;
+}
 
 
-  .v-list-group__items .v-list-item {
-    padding-inline-start: 30px !important;
-  }
+.v-list-item__content {
+  padding: 8px 0;
+}
 
 
-  .v-list-group--no-action > .v-list-group__header,
-  .v-list-item
-  {
-    border-left: 3px solid rgb(var(--v-theme-secondary));
-    height: 48px;
-  }
+.v-list-group__items .v-list-item {
+  padding-inline-start: 30px !important;
+}
 
 
-  .v-list-item:hover,
-  .v-list-item.active,
-  :deep(.v-list-group__items .v-list-item)
-  {
-    border-left: 3px solid rgb(var(--v-theme-primary));
-    background-color: rgb(var(--v-theme-secondary-alt)) !important;
-    color: rgb(var(--v-theme-on-secondary-alt)) !important;
-  }
+.v-list-group--no-action > .v-list-group__header,
+.v-list-item {
+  border-left: 3px solid rgb(var(--v-theme-secondary));
+  height: 48px;
+}
 
 
-  :deep(.v-list-group__items .v-list-item-title) {
-    color: rgb(var(--v-theme-on-secondary-alt));
-  }
+.v-list-item:hover,
+.v-list-item.active,
+:deep(.v-list-group__items .v-list-item) {
+  border-left: 3px solid rgb(var(--v-theme-primary));
+  background-color: rgb(var(--v-theme-secondary-alt)) !important;
+  color: rgb(var(--v-theme-on-secondary-alt)) !important;
+}
 
 
-  :deep(.v-list-group__items .v-icon) {
-    color: rgb(var(--v-theme-on-secondary-alt));
-  }
+:deep(.v-list-group__items .v-list-item-title) {
+  color: rgb(var(--v-theme-on-secondary-alt));
+}
 
 
-  :deep(.v-list-item .v-icon) {
-    margin-right: 10px;
-  }
+:deep(.v-list-group__items .v-icon) {
+  color: rgb(var(--v-theme-on-secondary-alt));
+}
 
 
-  :deep(.menu-item .fa) {
-    text-align: center;
-  }
+:deep(.v-list-item .v-icon) {
+  margin-right: 10px;
+}
 
 
+:deep(.menu-item .fa) {
+  text-align: center;
+}
 </style>
 </style>

+ 101 - 98
components/Layout/ParametersMenu.vue

@@ -1,5 +1,9 @@
 <template>
 <template>
-  <v-navigation-drawer v-if="displayMenu" v-model="isOpened" mobile-breakpoint="sm">
+  <v-navigation-drawer
+    v-if="displayMenu"
+    v-model="isOpened"
+    mobile-breakpoint="sm"
+  >
     <template v-slot:prepend>
     <template v-slot:prepend>
       <div class="title">
       <div class="title">
         <h3>{{ $t('parameters') }}</h3>
         <h3>{{ $t('parameters') }}</h3>
@@ -8,21 +12,22 @@
 
 
     <v-list active-class="active">
     <v-list active-class="active">
       <v-list-item
       <v-list-item
-          v-for="(item, i) in menu!!.children"
-          :key="i"
-          :title="$t(item.label)"
-          :prepend-icon="item.icon ? item.icon.name : ''"
-          :to="(item as MenuItem).to">
+        v-for="(item, i) in menu!!.children"
+        :key="i"
+        :title="$t(item.label)"
+        :prepend-icon="item.icon ? item.icon.name : ''"
+        :to="(item as MenuItem).to"
+      >
       </v-list-item>
       </v-list-item>
     </v-list>
     </v-list>
 
 
     <template v-slot:append>
     <template v-slot:append>
       <v-btn
       <v-btn
-          :href="homeUrl"
-          prepend-icon="fa fa-right-from-bracket"
-          :flat="true"
-          color="on-neutral-very-soft"
-          class="cancel-btn py-2"
+        :href="homeUrl"
+        prepend-icon="fa fa-right-from-bracket"
+        :flat="true"
+        color="on-neutral-very-soft"
+        class="cancel-btn py-2"
       >
       >
         {{ $t('exit') }}
         {{ $t('exit') }}
       </v-btn>
       </v-btn>
@@ -31,102 +36,100 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import {useMenu} from "~/composables/layout/useMenu";
-  import {useHomeUrl} from "~/composables/utils/useHomeUrl";
-  import {useDisplay} from "vuetify";
-  import {computed} from "@vue/reactivity";
-  import type {MenuGroup, MenuItem} from "~/types/layout";
+import { useMenu } from '~/composables/layout/useMenu'
+import { useHomeUrl } from '~/composables/utils/useHomeUrl'
+import { useDisplay } from 'vuetify'
+import { computed } from '@vue/reactivity'
+import type { MenuGroup, MenuItem } from '~/types/layout'
 
 
-  const { mdAndUp } = useDisplay()
+const { mdAndUp } = useDisplay()
 
 
-  const { getMenu, hasMenu, isMenuOpened, setMenuState } = useMenu()
+const { getMenu, hasMenu, isMenuOpened, setMenuState } = useMenu()
 
 
-  const menu: MenuGroup | null = getMenu('Parameters')
+const menu: MenuGroup | null = getMenu('Parameters')
 
 
-  const displayMenu = computed(() => {
-    return menu !== null && hasMenu('Parameters')
-  })
+const displayMenu = computed(() => {
+  return menu !== null && hasMenu('Parameters')
+})
 
 
-  const isOpened = computed(() => isMenuOpened('Parameters'))
+const isOpened = computed(() => isMenuOpened('Parameters'))
 
 
-  const unwatch = watch(mdAndUp, () => {
-    // Par défaut si l'écran est trop petit au chargement de la page, le menu doit rester fermé.
-    if (process.client && menu !== null) {
-      setMenuState('Parameters', mdAndUp.value)
-    }
-  })
+const unwatch = watch(mdAndUp, () => {
+  // Par défaut si l'écran est trop petit au chargement de la page, le menu doit rester fermé.
+  if (process.client && menu !== null) {
+    setMenuState('Parameters', mdAndUp.value)
+  }
+})
 
 
-  const { homeUrl } = useHomeUrl()
+const { homeUrl } = useHomeUrl()
 
 
-  onUnmounted(() => {
-    unwatch()
-  })
+onUnmounted(() => {
+  unwatch()
+})
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  .title {
-    display: flex;
-    align-items: center;
-    height: 48px;
-    vertical-align: center;
-    margin-top: 18px;
-    padding: 4px 16px;
-    font-size: 18px;
-    color: rgb(var(--v-theme-on-neutral-very-soft));
-  }
-
-  .v-navigation-drawer {
-    background-color: rgb(var(--v-theme-neutral-very-soft));
-    border-right: solid 1px rgb(var(--v-theme-neutral-strong));
-  }
-
-  :deep(.v-list-item-title), :deep(.v-icon)
-  {
-    font-size: 14px;
-    color: rgb(var(--v-theme-on-neutral-very-soft));
-  }
-
-  .v-list-item:hover,
-  .v-list-item.active,
-  :deep(.v-list-group__items .v-list-item)
-  {
-    background-color: rgb(var(--v-theme-neutral)) !important;
-    color: rgb(var(--v-theme-on-secondary-alt)) !important;
-  }
-
-  :deep(.v-list-item.active .v-list-item-title) {
-    font-weight: 800 !important;
-  }
-
-  :deep(.v-list-item-title), :deep(.v-icon)
-  {
-    font-size: 14px;
-    color: rgb(var(--v-theme-on-neutral-very-soft));
-  }
-
-  :deep(.v-list-item__prepend) {
-    margin: 10px 0;
-    margin-right: 10px !important;
-  }
-
-  :deep(.v-list-item .v-icon) {
-    max-width: 24px;
-    margin-right: 10px;
-  }
-
-  .cancel-btn {
-    height: 42px;
-    color: rgb(var(--v-theme-on-neutral-very-soft));
-    background-color: transparent;
-    width: 100%;
-    border-top: solid 1px rgb(var(--v-theme-on-neutral-very-soft));
-    display: flex;
-    flex-direction: row;
-    justify-content: flex-start;
-  }
-
-  :deep(.cancel-btn .v-btn__prepend) {
-    margin: 0 16px 4px 2px;
-  }
-
+.title {
+  display: flex;
+  align-items: center;
+  height: 48px;
+  vertical-align: center;
+  margin-top: 18px;
+  padding: 4px 16px;
+  font-size: 18px;
+  color: rgb(var(--v-theme-on-neutral-very-soft));
+}
+
+.v-navigation-drawer {
+  background-color: rgb(var(--v-theme-neutral-very-soft));
+  border-right: solid 1px rgb(var(--v-theme-neutral-strong));
+}
+
+:deep(.v-list-item-title),
+:deep(.v-icon) {
+  font-size: 14px;
+  color: rgb(var(--v-theme-on-neutral-very-soft));
+}
+
+.v-list-item:hover,
+.v-list-item.active,
+:deep(.v-list-group__items .v-list-item) {
+  background-color: rgb(var(--v-theme-neutral)) !important;
+  color: rgb(var(--v-theme-on-secondary-alt)) !important;
+}
+
+:deep(.v-list-item.active .v-list-item-title) {
+  font-weight: 800 !important;
+}
+
+:deep(.v-list-item-title),
+:deep(.v-icon) {
+  font-size: 14px;
+  color: rgb(var(--v-theme-on-neutral-very-soft));
+}
+
+:deep(.v-list-item__prepend) {
+  margin: 10px 0;
+  margin-right: 10px !important;
+}
+
+:deep(.v-list-item .v-icon) {
+  max-width: 24px;
+  margin-right: 10px;
+}
+
+.cancel-btn {
+  height: 42px;
+  color: rgb(var(--v-theme-on-neutral-very-soft));
+  background-color: transparent;
+  width: 100%;
+  border-top: solid 1px rgb(var(--v-theme-on-neutral-very-soft));
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
+}
+
+:deep(.cancel-btn .v-btn__prepend) {
+  margin: 0 16px 4px 2px;
+}
 </style>
 </style>

+ 45 - 35
components/Layout/SubHeader/ActivityYear.vue

@@ -1,8 +1,7 @@
 <template>
 <template>
   <main class="d-flex flex-row align-center">
   <main class="d-flex flex-row align-center">
-    <span v-show="mdAndUp"
-          class="mr-2 font-weight-bold on-neutral">
-        {{ $t(label) }} :
+    <span v-show="mdAndUp" class="mr-2 font-weight-bold on-neutral">
+      {{ $t(label) }} :
     </span>
     </span>
 
 
     <UiXeditableText
     <UiXeditableText
@@ -11,14 +10,17 @@
       :data="currentActivityYear"
       :data="currentActivityYear"
       @update="setActivityYear"
       @update="setActivityYear"
     >
     >
-      <template #xeditable.read="{inputValue}">
+      <template #xeditable.read="{ inputValue }">
         <div class="d-flex align-center on-neutral--clickable">
         <div class="d-flex align-center on-neutral--clickable">
-          <v-icon aria-hidden="false" size="small" class="mr-1" icon="fas fa-edit" />
-          <strong >
+          <v-icon
+            aria-hidden="false"
+            size="small"
+            class="mr-1"
+            icon="fas fa-edit"
+          />
+          <strong>
             {{ inputValue }}
             {{ inputValue }}
-            <span v-if="yearPlusOne">
-              / {{ parseInt(inputValue) + 1 }}
-            </span>
+            <span v-if="yearPlusOne"> / {{ parseInt(inputValue) + 1 }} </span>
           </strong>
           </strong>
         </div>
         </div>
       </template>
       </template>
@@ -27,14 +29,14 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import {useFormStore} from "~/stores/form";
-import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import Access from "~/models/Access/Access";
-import {useDisplay} from "vuetify";
-import {usePageStore} from "~/stores/page";
-import {useRefreshProfile} from "~/composables/data/useRefreshProfile";
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import { useFormStore } from '~/stores/form'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import Access from '~/models/Access/Access'
+import { useDisplay } from 'vuetify'
+import { usePageStore } from '~/stores/page'
+import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
 
 
 const { em } = useEntityManager()
 const { em } = useEntityManager()
 const accessProfileStore = useAccessProfileStore()
 const accessProfileStore = useAccessProfileStore()
@@ -44,9 +46,15 @@ const pageStore = usePageStore()
 const { mdAndUp } = useDisplay()
 const { mdAndUp } = useDisplay()
 const { refreshProfile } = useRefreshProfile()
 const { refreshProfile } = useRefreshProfile()
 
 
-const currentActivityYear: ComputedRef<number | undefined> = computed(() => accessProfileStore.activityYear ?? undefined)
+const currentActivityYear: ComputedRef<number | undefined> = computed(
+  () => accessProfileStore.activityYear ?? undefined,
+)
 const yearPlusOne: boolean = !organizationProfileStore.isManagerProduct
 const yearPlusOne: boolean = !organizationProfileStore.isManagerProduct
-const label: string = organizationProfileStore.isSchool ? 'schooling_year' : organizationProfileStore.isArtist ? 'season_year' : 'cotisation_year'
+const label: string = organizationProfileStore.isSchool
+  ? 'schooling_year'
+  : organizationProfileStore.isArtist
+    ? 'season_year'
+    : 'cotisation_year'
 
 
 /**
 /**
  * Persist a new activityYear
  * Persist a new activityYear
@@ -61,9 +69,11 @@ const setActivityYear = async (event: string) => {
   formStore.setDirty(false)
   formStore.setDirty(false)
 
 
   pageStore.loading = true
   pageStore.loading = true
-  await em.patch(Access, accessProfileStore.currentAccessId, { activityYear: activityYear })
+  await em.patch(Access, accessProfileStore.currentAccessId, {
+    activityYear: activityYear,
+  })
   if (process.server) {
   if (process.server) {
-      // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
+    // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
     await refreshProfile()
     await refreshProfile()
   }
   }
 
 
@@ -72,21 +82,21 @@ const setActivityYear = async (event: string) => {
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">
-  .activity-year-input {
-    width: 120px;
-    max-height: 20px;
+.activity-year-input {
+  width: 120px;
+  max-height: 20px;
 
 
-    .v-input {
-      min-width: 70px;
-    }
+  .v-input {
+    min-width: 70px;
+  }
 
 
-    input{
-      font-size: 14px;
-      width: 55px !important;
-      padding: 0 !important;
-      margin-top: 0 !important;
-      min-height: 24px;
-      height: 24px;
-    }
+  input {
+    font-size: 14px;
+    width: 55px !important;
+    padding: 0 !important;
+    margin-top: 0 !important;
+    min-height: 24px;
+    height: 24px;
   }
   }
+}
 </style>
 </style>

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

@@ -1,26 +1,24 @@
 <template>
 <template>
-  <v-breadcrumbs
-    :items="items"
-  />
+  <v-breadcrumbs :items="items" />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {computed} from "@vue/reactivity";
-import type {ComputedRef} from "@vue/reactivity";
-import type {AnyJson} from "~/types/data";
-import {useI18n} from "vue-i18n";
-import UrlUtils from "~/services/utils/urlUtils";
+import { computed } from '@vue/reactivity'
+import type { ComputedRef } from '@vue/reactivity'
+import type { AnyJson } from '~/types/data'
+import { useI18n } from 'vue-i18n'
+import UrlUtils from '~/services/utils/urlUtils'
 
 
 const runtimeConfig = useRuntimeConfig()
 const runtimeConfig = useRuntimeConfig()
 const i18n = useI18n()
 const i18n = useI18n()
 const router = useRouter()
 const router = useRouter()
 
 
 const items: ComputedRef<Array<AnyJson>> = computed(() => {
 const items: ComputedRef<Array<AnyJson>> = computed(() => {
-  const crumbs:Array<AnyJson> = []
+  const crumbs: Array<AnyJson> = []
 
 
   crumbs.push({
   crumbs.push({
     title: i18n.t('welcome'),
     title: i18n.t('welcome'),
-    href: UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#', 'dashboard')
+    href: UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#', 'dashboard'),
   })
   })
 
 
   const pathPart: Array<string> = UrlUtils.split(router.currentRoute.value.path)
   const pathPart: Array<string> = UrlUtils.split(router.currentRoute.value.path)
@@ -35,9 +33,11 @@ const items: ComputedRef<Array<AnyJson>> = computed(() => {
     match = router.resolve(path)
     match = router.resolve(path)
     if (match.name) {
     if (match.name) {
       crumbs.push({
       crumbs.push({
-        title: !parseInt(part, 10) ? i18n.t(part + '_breadcrumbs') : i18n.t('item'),
+        title: !parseInt(part, 10)
+          ? i18n.t(part + '_breadcrumbs')
+          : i18n.t('item'),
         exact: true,
         exact: true,
-        to: path
+        to: path,
       })
       })
     }
     }
   })
   })

+ 44 - 30
components/Layout/SubHeader/DataTiming.vue

@@ -1,6 +1,8 @@
 <template>
 <template>
   <main class="d-flex align-baseline">
   <main class="d-flex align-baseline">
-    <span v-show="mdAndUp" class="mr-2 font-weight-bold on-neutral">{{ $t('display_data') }} : </span>
+    <span v-show="mdAndUp" class="mr-2 font-weight-bold on-neutral"
+      >{{ $t('display_data') }} :
+    </span>
 
 
     <v-btn-toggle
     <v-btn-toggle
       ref="toggle"
       ref="toggle"
@@ -15,10 +17,15 @@
       @update:modelValue="onUpdate"
       @update:modelValue="onUpdate"
     >
     >
       <v-btn
       <v-btn
-          v-for="choice in historicalChoices"
-          :value="choice"
-          max-height="25"
-          :class="'font-weight-normal text-caption ' + (historicalValue.includes(choice) ? 'theme-primary' : 'theme-neutral-soft')"
+        v-for="choice in historicalChoices"
+        :value="choice"
+        max-height="25"
+        :class="
+          'font-weight-normal text-caption ' +
+          (historicalValue.includes(choice)
+            ? 'theme-primary'
+            : 'theme-neutral-soft')
+        "
       >
       >
         <!-- TODO: on ne devrait pas avoir besoin du if et de la classe 'btn-selected' dans v-btn, mais à l'heure
         <!-- TODO: on ne devrait pas avoir besoin du if et de la classe 'btn-selected' dans v-btn, mais à l'heure
          qu'il est, le component ne fonctionne pas comme attendu. A revoir quand vuetify 3 sera plus stable -->
          qu'il est, le component ne fonctionne pas comme attendu. A revoir quand vuetify 3 sera plus stable -->
@@ -29,14 +36,14 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useFormStore} from "~/stores/form";
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import type {Ref} from "@vue/reactivity";
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import {useDisplay, useTheme} from "vuetify";
-import Access from "~/models/Access/Access";
-import {usePageStore} from "~/stores/page";
-import {useRefreshProfile} from "~/composables/data/useRefreshProfile";
+import { useFormStore } from '~/stores/form'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import type { Ref } from '@vue/reactivity'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import { useDisplay, useTheme } from 'vuetify'
+import Access from '~/models/Access/Access'
+import { usePageStore } from '~/stores/page'
+import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
 
 
 // TODO: en v3.0.5, pas de solution documentée pour renseigner directement la couleur dans le template, à revoir
 // TODO: en v3.0.5, pas de solution documentée pour renseigner directement la couleur dans le template, à revoir
 const color = useTheme().current.value.colors['primary']
 const color = useTheme().current.value.colors['primary']
@@ -50,9 +57,15 @@ const { refreshProfile } = useRefreshProfile()
 
 
 const toggle = ref(null)
 const toggle = ref(null)
 
 
-const historicalChoices: Array<'past' | 'present' | 'future'> = ['past', 'present', 'future']
+const historicalChoices: Array<'past' | 'present' | 'future'> = [
+  'past',
+  'present',
+  'future',
+]
 
 
-const historicalValue: Ref<Array<string>> = ref(historicalChoices.filter((item) => accessProfileStore.historical[item]))
+const historicalValue: Ref<Array<string>> = ref(
+  historicalChoices.filter((item) => accessProfileStore.historical[item]),
+)
 
 
 const onUpdate = async (newValue: Array<string>) => {
 const onUpdate = async (newValue: Array<string>) => {
   historicalValue.value = newValue
   historicalValue.value = newValue
@@ -60,35 +73,36 @@ const onUpdate = async (newValue: Array<string>) => {
   const accessId = accessProfileStore.currentAccessId
   const accessId = accessProfileStore.currentAccessId
 
 
   accessProfileStore.setHistorical(
   accessProfileStore.setHistorical(
-      historicalValue.value.includes('past'),
-      historicalValue.value.includes('present'),
-      historicalValue.value.includes('future')
+    historicalValue.value.includes('past'),
+    historicalValue.value.includes('present'),
+    historicalValue.value.includes('future'),
   )
   )
 
 
   setDirty(false)
   setDirty(false)
   pageStore.loading = true
   pageStore.loading = true
 
 
-  await em.patch(Access, accessId, {'historical': accessProfileStore.historical})
+  await em.patch(Access, accessId, {
+    historical: accessProfileStore.historical,
+  })
   if (process.server) {
   if (process.server) {
-      // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
+    // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
     await refreshProfile()
     await refreshProfile()
   }
   }
 
 
   window.location.reload()
   window.location.reload()
 }
 }
-
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  .v-btn-group {
-    max-height: 22px;
-  }
+.v-btn-group {
+  max-height: 22px;
+}
 
 
-  .v-btn {
-    padding: 0 8px;
-  }
+.v-btn {
+  padding: 0 8px;
+}
 
 
-  .v-btn.btn-selected {
-    background-color: rgb(var(--v-theme-primary)) !important;
-  }
+.v-btn.btn-selected {
+  background-color: rgb(var(--v-theme-primary)) !important;
+}
 </style>
 </style>

+ 25 - 18
components/Layout/SubHeader/DataTimingRange.vue

@@ -6,23 +6,23 @@
       </span>
       </span>
 
 
       <UiDateRangePicker
       <UiDateRangePicker
-          :model-value="datesRange"
-          :max-height="28"
-          @update:model-value="updateDateTimeRange"
+        :model-value="datesRange"
+        :max-height="28"
+        @update:model-value="updateDateTimeRange"
       />
       />
     </div>
     </div>
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import type {Ref} from "@vue/reactivity";
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import {useFormStore} from "~/stores/form";
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import Access from "~/models/Access/Access";
-import DateUtils from "~/services/utils/dateUtils";
-import {usePageStore} from "~/stores/page";
-import {useRefreshProfile} from "~/composables/data/useRefreshProfile";
+import type { Ref } from '@vue/reactivity'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import { useFormStore } from '~/stores/form'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import Access from '~/models/Access/Access'
+import DateUtils from '~/services/utils/dateUtils'
+import { usePageStore } from '~/stores/page'
+import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
 
 
 const { setDirty } = useFormStore()
 const { setDirty } = useFormStore()
 const accessProfileStore = useAccessProfileStore()
 const accessProfileStore = useAccessProfileStore()
@@ -33,18 +33,23 @@ const { refreshProfile } = useRefreshProfile()
 const start = accessProfileStore.historical.dateStart
 const start = accessProfileStore.historical.dateStart
 const end = accessProfileStore.historical.dateEnd
 const end = accessProfileStore.historical.dateEnd
 
 
-const datesRange: Ref<Array<Date> | null> = ref((start && end) ? [new Date(start), new Date(end)] : null)
+const datesRange: Ref<Array<Date> | null> = ref(
+  start && end ? [new Date(start), new Date(end)] : null,
+)
 
 
 const updateDateTimeRange = async (dates: Array<Date>): Promise<any> => {
 const updateDateTimeRange = async (dates: Array<Date>): Promise<any> => {
-
   const accessId = accessProfileStore.currentAccessId
   const accessId = accessProfileStore.currentAccessId
 
 
   datesRange.value = dates
   datesRange.value = dates
 
 
-  if (datesRange.value !== null && datesRange.value[0] !== null && datesRange.value[1] !== null) {
+  if (
+    datesRange.value !== null &&
+    datesRange.value[0] !== null &&
+    datesRange.value[1] !== null
+  ) {
     accessProfileStore.setHistoricalRange(
     accessProfileStore.setHistoricalRange(
-        DateUtils.formatIsoShortDate(datesRange.value[0]),
-        DateUtils.formatIsoShortDate(datesRange.value[1])
+      DateUtils.formatIsoShortDate(datesRange.value[0]),
+      DateUtils.formatIsoShortDate(datesRange.value[1]),
     )
     )
   } else {
   } else {
     accessProfileStore.setHistorical(false, true, false)
     accessProfileStore.setHistorical(false, true, false)
@@ -52,9 +57,11 @@ const updateDateTimeRange = async (dates: Array<Date>): Promise<any> => {
   setDirty(false)
   setDirty(false)
   pageStore.loading = true
   pageStore.loading = true
 
 
-  await em.patch(Access, accessId, {'historical': accessProfileStore.historical})
+  await em.patch(Access, accessId, {
+    historical: accessProfileStore.historical,
+  })
   if (process.server) {
   if (process.server) {
-      // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
+    // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
     await refreshProfile()
     await refreshProfile()
   }
   }
 
 

+ 45 - 39
components/Layout/SubHeader/PersonnalizedList.vue

@@ -5,12 +5,17 @@
     </a>
     </a>
 
 
     <v-menu
     <v-menu
-        :activator="btn"
-        offset="10"
-        min-width="440"
-        :close-on-content-click="false"
+      :activator="btn"
+      offset="10"
+      min-width="440"
+      :close-on-content-click="false"
     >
     >
-      <v-card v-if="collection.totalItems === 0" height="80" width="440" class="pa-4">
+      <v-card
+        v-if="collection.totalItems === 0"
+        height="80"
+        width="440"
+        class="pa-4"
+      >
         <v-card-text class="ma-0 pa-0 header_menu">
         <v-card-text class="ma-0 pa-0 header_menu">
           {{ $t('nothing_to_show') }}
           {{ $t('nothing_to_show') }}
         </v-card-text>
         </v-card-text>
@@ -19,11 +24,11 @@
       <v-card v-else width="440">
       <v-card v-else width="440">
         <v-card-title class="text-body-2 header-personalized">
         <v-card-title class="text-body-2 header-personalized">
           <v-text-field
           <v-text-field
-              v-model="search"
-              :label="$t('searchList')"
-              :loading="pending"
-              density="compact"
-              clear-icon="header-personalized"
+            v-model="search"
+            :label="$t('searchList')"
+            :loading="pending"
+            density="compact"
+            clear-icon="header-personalized"
           />
           />
         </v-card-title>
         </v-card-title>
 
 
@@ -36,24 +41,23 @@
               :href="getListURL(item)"
               :href="getListURL(item)"
               exact
               exact
             >
             >
-              <strong>{{item.menuKey}}</strong> - {{item.label}}
+              <strong>{{ item.menuKey }}</strong> - {{ item.label }}
             </v-list-item>
             </v-list-item>
           </v-list>
           </v-list>
         </v-card-text>
         </v-card-text>
       </v-card>
       </v-card>
-
     </v-menu>
     </v-menu>
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import PersonalizedList from '~/models/Access/PersonalizedList'
 import PersonalizedList from '~/models/Access/PersonalizedList'
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {ref} from "@vue/reactivity";
-import type {ComputedRef, Ref} from "@vue/reactivity";
-import type {AnyJson} from "~/types/data";
-import ApiResource from "~/models/ApiResource";
-import UrlUtils from "~/services/utils/urlUtils";
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import { ref } from '@vue/reactivity'
+import type { ComputedRef, Ref } from '@vue/reactivity'
+import type { AnyJson } from '~/types/data'
+import ApiResource from '~/models/ApiResource'
+import UrlUtils from '~/services/utils/urlUtils'
 
 
 const btn: Ref = ref(null)
 const btn: Ref = ref(null)
 
 
@@ -64,24 +68,26 @@ const { data: collection, pending } = await fetchCollection(PersonalizedList)
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const items: ComputedRef<Array<AnyJson>> = computed(() => {
 const items: ComputedRef<Array<AnyJson>> = computed(() => {
-  const lists: Array<ApiResource> = collection.value !== null ? collection.value.items : []
+  const lists: Array<ApiResource> =
+    collection.value !== null ? collection.value.items : []
 
 
-  lists.map(item => {
+  lists.map((item) => {
     item.menuKey = i18n.t(item.menuKey) as string
     item.menuKey = i18n.t(item.menuKey) as string
   })
   })
 
 
   return lists
   return lists
 })
 })
 
 
-const search = ref('');
+const search = ref('')
 
 
 const filteredItems = computed(() => {
 const filteredItems = computed(() => {
-  return items.value.filter( item => {
-      return !search.value ||
-             item.label.toLowerCase().indexOf(search.value.toLowerCase()) >= 0 ||
-             item.menuKey.toLowerCase().indexOf(search.value.toLowerCase()) >= 0
-    }
-  )
+  return items.value.filter((item) => {
+    return (
+      !search.value ||
+      item.label.toLowerCase().indexOf(search.value.toLowerCase()) >= 0 ||
+      item.menuKey.toLowerCase().indexOf(search.value.toLowerCase()) >= 0
+    )
+  })
 })
 })
 
 
 const runtimeConfig = useRuntimeConfig()
 const runtimeConfig = useRuntimeConfig()
@@ -93,16 +99,16 @@ const getListURL = (list: PersonalizedList) => {
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  #activator {
-    cursor: pointer;
-  }
-
-  #activator:hover {
-    color: rgb(var(--var-theme-on-neutral)) !important;
-  }
-
-  .header-personalized {
-    margin-bottom: 0;
-    padding-bottom: 0;
-  }
+#activator {
+  cursor: pointer;
+}
+
+#activator:hover {
+  color: rgb(var(--var-theme-on-neutral)) !important;
+}
+
+.header-personalized {
+  margin-bottom: 0;
+  padding-bottom: 0;
+}
 </style>
 </style>

+ 55 - 42
components/Layout/Subheader.vue

@@ -15,40 +15,52 @@ Contient entre autres le breadcrumb, les commandes de changement d'année et les
 
 
       <span class="flex-fill" />
       <span class="flex-fill" />
 
 
-      <v-card
-        class="d-flex flex-row align-center mr-6"
-        :flat="true"
-        tile
-      >
-        <LayoutSubHeaderActivityYear v-if="smAndUp && !showDateTimeRange" class="activity-year" />
-
-        <div v-if="hasMenuOrIsTeacher" class="d-flex flex-row align-center h-100">
+      <v-card class="d-flex flex-row align-center mr-6" :flat="true" tile>
+        <LayoutSubHeaderActivityYear
+          v-if="smAndUp && !showDateTimeRange"
+          class="activity-year"
+        />
+
+        <div
+          v-if="hasMenuOrIsTeacher"
+          class="d-flex flex-row align-center h-100"
+        >
           <LayoutSubHeaderDataTiming
           <LayoutSubHeaderDataTiming
-              v-if="smAndUp && !showDateTimeRange"
-              class="data-timing ml-2"
+            v-if="smAndUp && !showDateTimeRange"
+            class="data-timing ml-2"
           />
           />
 
 
           <LayoutSubHeaderDataTimingRange
           <LayoutSubHeaderDataTimingRange
-              v-if="smAndUp && showDateTimeRange"
-              class="data-timing-range ml-n1"
+            v-if="smAndUp && showDateTimeRange"
+            class="data-timing-range ml-n1"
           />
           />
 
 
           <v-btn
           <v-btn
-              v-if="smAndUp"
-              ref="btn"
-              class="switch-btn ml-1 theme-neutral-soft"
-              height="22" min-height="22" max-height="22"
-              width="25" min-width="25" max-width="25"
-              elevation="0"
-              @click="showDateTimeRange = !showDateTimeRange"
+            v-if="smAndUp"
+            ref="btn"
+            class="switch-btn ml-1 theme-neutral-soft"
+            height="22"
+            min-height="22"
+            max-height="22"
+            width="25"
+            min-width="25"
+            max-width="25"
+            elevation="0"
+            @click="showDateTimeRange = !showDateTimeRange"
           >
           >
-              <v-icon icon="fas fa-history" class="font-weight-normal" style="font-size: 14px;" />
+            <v-icon
+              icon="fas fa-history"
+              class="font-weight-normal"
+              style="font-size: 14px"
+            />
           </v-btn>
           </v-btn>
           <v-tooltip location="bottom" :activator="btn">
           <v-tooltip location="bottom" :activator="btn">
-              <span>{{ $t('history_help') }}</span>
+            <span>{{ $t('history_help') }}</span>
           </v-tooltip>
           </v-tooltip>
 
 
-          <LayoutSubHeaderPersonnalizedList class="personalized-list ml-2 d-flex align-center" />
+          <LayoutSubHeaderPersonnalizedList
+            class="personalized-list ml-2 d-flex align-center"
+          />
         </div>
         </div>
       </v-card>
       </v-card>
     </v-card>
     </v-card>
@@ -56,29 +68,30 @@ Contient entre autres le breadcrumb, les commandes de changement d'année et les
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-    import {useAccessProfileStore} from "~/stores/accessProfile";
-    import {computed, ref} from "@vue/reactivity";
-    import type {ComputedRef, Ref} from "@vue/reactivity";
-    import {useMenu} from "~/composables/layout/useMenu";
-    import {useDisplay} from "vuetify";
-
-    const { smAndUp, lgAndUp } = useDisplay()
-    const accessProfile = useAccessProfileStore()
-    const { hasMenu } = useMenu()
-    const btn: Ref = ref(null)
-
-    const hasMenuOrIsTeacher: ComputedRef<boolean> = computed(
-        () => hasMenu('Main') || (accessProfile.isTeacher ?? false)
-    )
-
-    const showDateTimeRange: Ref<boolean> = ref(
-        Object.hasOwn(accessProfile.historical, 'dateStart') && accessProfile.historical.dateStart !== null &&
-        Object.hasOwn(accessProfile.historical, 'dateEnd') && accessProfile.historical.dateEnd !== null
-    )
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import { computed, ref } from '@vue/reactivity'
+import type { ComputedRef, Ref } from '@vue/reactivity'
+import { useMenu } from '~/composables/layout/useMenu'
+import { useDisplay } from 'vuetify'
+
+const { smAndUp, lgAndUp } = useDisplay()
+const accessProfile = useAccessProfileStore()
+const { hasMenu } = useMenu()
+const btn: Ref = ref(null)
+
+const hasMenuOrIsTeacher: ComputedRef<boolean> = computed(
+  () => hasMenu('Main') || (accessProfile.isTeacher ?? false),
+)
+
+const showDateTimeRange: Ref<boolean> = ref(
+  Object.hasOwn(accessProfile.historical, 'dateStart') &&
+    accessProfile.historical.dateStart !== null &&
+    Object.hasOwn(accessProfile.historical, 'dateEnd') &&
+    accessProfile.historical.dateEnd !== null,
+)
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-
 main {
 main {
   font-size: 12px;
   font-size: 12px;
 }
 }

+ 14 - 14
components/Layout/ThemeSwitcher.vue

@@ -1,26 +1,26 @@
 <template>
 <template>
   <v-switch
   <v-switch
-      v-model="theme.global.name.value"
-      density="compact"
-      :inline="true"
-      false-value="light"
-      false-icon="fas fa-sun"
-      true-value="dark"
-      true-icon="fas fa-moon"
+    v-model="theme.global.name.value"
+    density="compact"
+    :inline="true"
+    false-value="light"
+    false-icon="fas fa-sun"
+    true-value="dark"
+    true-icon="fas fa-moon"
   />
   />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useTheme} from "vuetify";
+import { useTheme } from 'vuetify'
 
 
 const theme = useTheme()
 const theme = useTheme()
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  .v-switch {
-    min-width: 60px;
-    max-width: 60px;
-    min-height: 40px;
-    max-height: 40px;
-  }
+.v-switch {
+  min-width: 60px;
+  max-width: 60px;
+  min-height: 40px;
+  max-height: 40px;
+}
 </style>
 </style>

+ 27 - 27
components/Ui/Button/Delete.vue

@@ -8,9 +8,7 @@ Bouton Delete avec modale de confirmation de la suppression
       <v-icon>fas fa-trash</v-icon>
       <v-icon>fas fa-trash</v-icon>
     </v-btn>
     </v-btn>
 
 
-    <LazyLayoutDialog
-      :show="showDialog"
-    >
+    <LazyLayoutDialog :show="showDialog">
       <template #dialogType>{{ $t('delete_assistant') }}</template>
       <template #dialogType>{{ $t('delete_assistant') }}</template>
       <template #dialogTitle>{{ $t('caution') }}</template>
       <template #dialogTitle>{{ $t('caution') }}</template>
       <template #dialogText>
       <template #dialogText>
@@ -31,27 +29,27 @@ Bouton Delete avec modale de confirmation de la suppression
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {TYPE_ALERT} from '~/types/enum/enums'
-import type {Ref} from "@vue/reactivity";
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import ApiResource from "~/models/ApiResource";
-import {usePageStore} from "~/stores/page";
-import ApiModel from "~/models/ApiModel";
+import { TYPE_ALERT } from '~/types/enum/enums'
+import type { Ref } from '@vue/reactivity'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import ApiResource from '~/models/ApiResource'
+import { usePageStore } from '~/stores/page'
+import ApiModel from '~/models/ApiModel'
 
 
 const props = defineProps({
 const props = defineProps({
-    model: {
-      type: Function as any as () => typeof ApiModel,
-      required: true
-    },
-    entity: {
-      type: Object as () => ApiResource,
-      required: true
-    },
-    flat: {
-      type: Boolean,
-      required: false,
-      default: false
-    }
+  model: {
+    type: Function as any as () => typeof ApiModel,
+    required: true,
+  },
+  entity: {
+    type: Object as () => ApiResource,
+    required: true,
+  },
+  flat: {
+    type: Boolean,
+    required: false,
+    default: false,
+  },
 })
 })
 
 
 const showDialog: Ref<boolean> = ref(false)
 const showDialog: Ref<boolean> = ref(false)
@@ -70,10 +68,12 @@ const deleteItem = async () => {
   showDialog.value = false
   showDialog.value = false
 }
 }
 
 
-const alertDeleteItem = () => { showDialog.value = true }
-const closeDialog = () => { showDialog.value = false }
-
+const alertDeleteItem = () => {
+  showDialog.value = true
+}
+const closeDialog = () => {
+  showDialog.value = false
+}
 </script>
 </script>
 
 
-<style scoped>
-</style>
+<style scoped></style>

+ 24 - 23
components/Ui/Button/Submit.vue

@@ -1,12 +1,11 @@
 <template>
 <template>
   <v-btn
   <v-btn
-      class="mr-4 theme-primary"
-      :class="hasOtherActions ? 'pr-0' : ''"
-      @click="submitAction(mainAction)"
-      ref="mainBtn"
-      :disabled="validationPending"
+    class="mr-4 theme-primary"
+    :class="hasOtherActions ? 'pr-0' : ''"
+    @click="submitAction(mainAction)"
+    ref="mainBtn"
+    :disabled="validationPending"
   >
   >
-
     {{ $t(mainAction) }}
     {{ $t(mainAction) }}
 
 
     <v-divider class="ml-3" :vertical="true" v-if="hasOtherActions"></v-divider>
     <v-divider class="ml-3" :vertical="true" v-if="hasOtherActions"></v-divider>
@@ -22,13 +21,13 @@
       <template #activator="{ on, attrs }">
       <template #activator="{ on, attrs }">
         <v-toolbar-title v-on="on">
         <v-toolbar-title v-on="on">
           <v-icon class="pl-3 pr-3">
           <v-icon class="pl-3 pr-3">
-            {{ dropDirection === 'top' ? 'fa fa-caret-up' : 'fa fa-caret-down'}}
+            {{
+              dropDirection === 'top' ? 'fa fa-caret-up' : 'fa fa-caret-down'
+            }}
           </v-icon>
           </v-icon>
         </v-toolbar-title>
         </v-toolbar-title>
       </template>
       </template>
-      <v-list
-        :min-width="menuSize"
-      >
+      <v-list :min-width="menuSize">
         <v-list-item
         <v-list-item
           dense
           dense
           v-for="(action, index) in actions"
           v-for="(action, index) in actions"
@@ -36,7 +35,10 @@
           class="subAction"
           class="subAction"
           v-if="index > 0"
           v-if="index > 0"
         >
         >
-          <v-list-item-title v-text="$t(action)" @click="submitAction(action)" />
+          <v-list-item-title
+            v-text="$t(action)"
+            @click="submitAction(action)"
+          />
         </v-list-item>
         </v-list-item>
       </v-list>
       </v-list>
     </v-menu>
     </v-menu>
@@ -44,30 +46,30 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {computed, ref} from "@vue/reactivity";
-import type {ComputedRef, Ref} from "@vue/reactivity";
+import { computed, ref } from '@vue/reactivity'
+import type { ComputedRef, Ref } from '@vue/reactivity'
 
 
 const props = defineProps({
 const props = defineProps({
   actions: {
   actions: {
     type: Array,
     type: Array,
-    required: true
+    required: true,
   },
   },
   dropDirection: {
   dropDirection: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default:'bottom'
+    default: 'bottom',
   },
   },
   validationPending: {
   validationPending: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
-  }
+    default: false,
+  },
 })
 })
 
 
 const emit = defineEmits(['submit'])
 const emit = defineEmits(['submit'])
 
 
 const mainBtn: Ref = ref(null)
 const mainBtn: Ref = ref(null)
-const menuSize = computed(()=>{
+const menuSize = computed(() => {
   // Btn size + 40px de padding
   // Btn size + 40px de padding
   return mainBtn.value?.$el.clientWidth + 40
   return mainBtn.value?.$el.clientWidth + 40
 })
 })
@@ -76,21 +78,20 @@ const submitAction = (action: string) => {
   emit('submit', action)
   emit('submit', action)
 }
 }
 
 
-const mainAction: ComputedRef<string> = computed(()=>{
+const mainAction: ComputedRef<string> = computed(() => {
   return props.actions[0] as string
   return props.actions[0] as string
 })
 })
 
 
-const hasOtherActions: ComputedRef<boolean> = computed(()=>{
+const hasOtherActions: ComputedRef<boolean> = computed(() => {
   return props.actions.length > 1
   return props.actions.length > 1
 })
 })
-
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.v-list-item--dense{
+.v-list-item--dense {
   min-height: 25px;
   min-height: 25px;
 }
 }
-.subAction{
+.subAction {
   cursor: pointer;
   cursor: pointer;
 }
 }
 </style>
 </style>

+ 8 - 16
components/Ui/Card.vue

@@ -3,12 +3,7 @@ Container de type Card
 -->
 -->
 
 
 <template>
 <template>
-  <v-card
-    elevation="2"
-    outlined
-    shaped
-    min-height="200"
-  >
+  <v-card elevation="2" outlined shaped min-height="200">
     <!-- Titre -->
     <!-- Titre -->
     <v-card-title>
     <v-card-title>
       <slot name="card.title" />
       <slot name="card.title" />
@@ -33,32 +28,29 @@ Container de type Card
 
 
       <slot name="card.action" />
       <slot name="card.action" />
     </v-card-actions>
     </v-card-actions>
-
   </v-card>
   </v-card>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
 const props = defineProps({
 const props = defineProps({
   link: {
   link: {
     type: String,
     type: String,
-    required: true
+    required: true,
   },
   },
   model: {
   model: {
     type: Object,
     type: Object,
-    required: true
+    required: true,
   },
   },
   entity: {
   entity: {
     type: Object,
     type: Object,
-    required: true
+    required: true,
   },
   },
-  withDeleteAction:{
+  withDeleteAction: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: true
-  }
+    default: true,
+  },
 })
 })
 </script>
 </script>
 
 
-<style scoped>
-</style>
+<style scoped></style>

+ 18 - 16
components/Ui/Collection.vue

@@ -4,15 +4,14 @@
   <main>
   <main>
     <v-skeleton-loader v-if="pending" :type="loaderType" />
     <v-skeleton-loader v-if="pending" :type="loaderType" />
     <div v-else>
     <div v-else>
-
       <!-- Content -->
       <!-- Content -->
-      <slot name="list.item" v-bind="{items}" />
+      <slot name="list.item" v-bind="{ items }" />
 
 
       <!-- New button -->
       <!-- New button -->
       <v-btn v-if="newLink" class="theme-primary float-right">
       <v-btn v-if="newLink" class="theme-primary float-right">
         <NuxtLink :to="newLink" class="no-decoration">
         <NuxtLink :to="newLink" class="no-decoration">
           <v-icon>fa-plus-circle</v-icon>
           <v-icon>fa-plus-circle</v-icon>
-          <span>{{$t('add')}}</span>
+          <span>{{ $t('add') }}</span>
         </NuxtLink>
         </NuxtLink>
       </v-btn>
       </v-btn>
     </div>
     </div>
@@ -21,38 +20,41 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {computed, toRefs} from "@vue/reactivity";
-import type {ComputedRef, ToRefs} from "@vue/reactivity";
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import type {Collection} from "~/types/data";
+import { computed, toRefs } from '@vue/reactivity'
+import type { ComputedRef, ToRefs } from '@vue/reactivity'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import type { Collection } from '~/types/data'
 
 
 const props = defineProps({
 const props = defineProps({
   model: {
   model: {
     type: Object,
     type: Object,
-    required: true
+    required: true,
   },
   },
   parent: {
   parent: {
     type: Object,
     type: Object,
-    required: false
+    required: false,
   },
   },
   loaderType: {
   loaderType: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: 'text'
+    default: 'text',
   },
   },
   newLink: {
   newLink: {
     type: String,
     type: String,
-    required: false
-  }
+    required: false,
+  },
 })
 })
 
 
 const { model, parent }: ToRefs = toRefs(props)
 const { model, parent }: ToRefs = toRefs(props)
 
 
 const { fetchCollection } = useEntityFetch()
 const { fetchCollection } = useEntityFetch()
 
 
-const { data: collection, pending } = await fetchCollection(model.value, parent.value)
-
-const items: ComputedRef<Collection> = computed(() => collection.value ?? { items: [], pagination: {}, totalItems: 0 })
+const { data: collection, pending } = await fetchCollection(
+  model.value,
+  parent.value,
+)
 
 
+const items: ComputedRef<Collection> = computed(
+  () => collection.value ?? { items: [], pagination: {}, totalItems: 0 },
+)
 </script>
 </script>

+ 17 - 29
components/Ui/DataTable.vue

@@ -5,10 +5,7 @@ Tableau interactif conçu pour l'affichage d'une collection d'entités
 -->
 -->
 
 
 <template>
 <template>
-  <v-col
-    cols="12"
-    sm="12"
-  >
+  <v-col cols="12" sm="12">
     <v-data-table
     <v-data-table
       :headers="headersWithItem"
       :headers="headersWithItem"
       :items="collection.items"
       :items="collection.items"
@@ -23,51 +20,39 @@ Tableau interactif conçu pour l'affichage d'une collection d'entités
       </template>
       </template>
 
 
       <template #item.actions="{ item }">
       <template #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>
+        <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-data-table>
     </v-data-table>
   </v-col>
   </v-col>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {ref, toRefs} from "@vue/reactivity";
-import type {Ref} from "@vue/reactivity";
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import ApiResource from "~/models/ApiResource";
-import type {AnyJson} from "~/types/data";
+import { ref, toRefs } from '@vue/reactivity'
+import type { Ref } from '@vue/reactivity'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import ApiResource from '~/models/ApiResource'
+import type { AnyJson } from '~/types/data'
 
 
 const props = defineProps({
 const props = defineProps({
   parent: {
   parent: {
     type: Object,
     type: Object,
-    required: true
+    required: true,
   },
   },
   model: {
   model: {
     type: Object,
     type: Object,
-    required: true
+    required: true,
   },
   },
   headers: {
   headers: {
     type: Array,
     type: Array,
-    required: true
-  }
+    required: true,
+  },
 })
 })
 
 
 const { parent, model, headers } = toRefs(props)
 const { parent, model, headers } = toRefs(props)
 
 
 const headersWithItem = computed(() => {
 const headersWithItem = computed(() => {
-  return headers.value.map((header:any) => {
+  return headers.value.map((header: any) => {
     header.item = 'item.' + header.value
     header.item = 'item.' + header.value
     return header
     return header
   })
   })
@@ -78,7 +63,10 @@ const entries: Ref<Array<AnyJson>> = ref(Array<AnyJson>())
 
 
 const { fetchCollection } = useEntityFetch()
 const { fetchCollection } = useEntityFetch()
 
 
-const { data: collection, pending } = await fetchCollection(model.value as typeof ApiResource, parent.value as ApiResource)
+const { data: collection, pending } = await fetchCollection(
+  model.value as typeof ApiResource,
+  parent.value as ApiResource,
+)
 
 
 const itemId: Ref<number> = ref(0)
 const itemId: Ref<number> = ref(0)
 
 

+ 25 - 28
components/Ui/DatePicker.vue

@@ -7,26 +7,26 @@ Sélecteur de dates
 <template>
 <template>
   <main>
   <main>
     <VueDatePicker
     <VueDatePicker
-        :model-value="modelValue"
-        :locale="i18n.locale.value"
-        :format-locale="fnsLocale"
-        :format="dateFormat"
-        :enable-time-picker="withTime"
-        :teleport="true"
-        text-input
-        :auto-apply="true"
-        :select-text="$t('select')"
-        :cancel-text="$t('cancel')"
-        :disabled="readonly"
-        :position="position"
-        @update:model-value="onUpdate"
+      :model-value="modelValue"
+      :locale="i18n.locale.value"
+      :format-locale="fnsLocale"
+      :format="dateFormat"
+      :enable-time-picker="withTime"
+      :teleport="true"
+      text-input
+      :auto-apply="true"
+      :select-text="$t('select')"
+      :cancel-text="$t('cancel')"
+      :disabled="readonly"
+      :position="position"
+      @update:model-value="onUpdate"
     />
     />
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import DateUtils, {supportedLocales} from "~/services/utils/dateUtils";
-import type {PropType} from "@vue/runtime-core";
+import DateUtils, { supportedLocales } from '~/services/utils/dateUtils'
+import type { PropType } from '@vue/runtime-core'
 
 
 const i18n = useI18n()
 const i18n = useI18n()
 
 
@@ -36,22 +36,22 @@ const props = defineProps({
   modelValue: {
   modelValue: {
     type: Object as PropType<Date>,
     type: Object as PropType<Date>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   format: {
   format: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   withTime: {
   withTime: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * @see https://vue3datepicker.com/props/positioning/#position
    * @see https://vue3datepicker.com/props/positioning/#position
@@ -59,13 +59,13 @@ const props = defineProps({
   position: {
   position: {
     type: String as PropType<'left' | 'center' | 'right'>,
     type: String as PropType<'left' | 'center' | 'right'>,
     required: false,
     required: false,
-    default: 'center'
-  }
+    default: 'center',
+  },
 })
 })
 
 
-const defaultFormatPattern = props.withTime ?
-    DateUtils.getFormatPattern(i18n.locale.value as supportedLocales) :
-    DateUtils.getShortFormatPattern(i18n.locale.value as supportedLocales)
+const defaultFormatPattern = props.withTime
+  ? DateUtils.getFormatPattern(i18n.locale.value as supportedLocales)
+  : DateUtils.getShortFormatPattern(i18n.locale.value as supportedLocales)
 
 
 const dateFormat: Ref<string> = ref(props.format ?? defaultFormatPattern)
 const dateFormat: Ref<string> = ref(props.format ?? defaultFormatPattern)
 
 
@@ -74,9 +74,6 @@ const emit = defineEmits(['update:model-value'])
 const onUpdate = (event: Date) => {
 const onUpdate = (event: Date) => {
   emit('update:model-value', event)
   emit('update:model-value', event)
 }
 }
-
 </script>
 </script>
 
 
-<style scoped>
-
-</style>
+<style scoped></style>

+ 69 - 66
components/Ui/DateRangePicker.vue

@@ -1,43 +1,43 @@
 <template>
 <template>
   <!-- @see https://vue3datepicker.com/props/modes/#multi-calendars -->
   <!-- @see https://vue3datepicker.com/props/modes/#multi-calendars -->
   <VueDatePicker
   <VueDatePicker
-      :model-value="modelValue"
-      range
-      multi-calendars
-      :auto-apply="autoApply"
-      :locale="i18n.locale.value"
-      :format-locale="fnsLocale"
-      :format="dateFormatPattern"
-      :start-date="today"
-      :teleport="true"
-      :alt-position="dateRangePickerAltPosition"
-      :enable-time-picker="false"
-      close-on-scroll
-      text-input
-      :select-text="$t('select')"
-      :cancel-text="$t('cancel')"
-      input-class-name="date-range-picker-input"
-      @update:model-value="updateDateTimeRange"
-      class="date-range-picker"
-      :style="style"
+    :model-value="modelValue"
+    range
+    multi-calendars
+    :auto-apply="autoApply"
+    :locale="i18n.locale.value"
+    :format-locale="fnsLocale"
+    :format="dateFormatPattern"
+    :start-date="today"
+    :teleport="true"
+    :alt-position="dateRangePickerAltPosition"
+    :enable-time-picker="false"
+    close-on-scroll
+    text-input
+    :select-text="$t('select')"
+    :cancel-text="$t('cancel')"
+    input-class-name="date-range-picker-input"
+    @update:model-value="updateDateTimeRange"
+    class="date-range-picker"
+    :style="style"
   />
   />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import DateUtils, {supportedLocales} from "~/services/utils/dateUtils";
-import type {PropType} from "@vue/runtime-core";
+import DateUtils, { supportedLocales } from '~/services/utils/dateUtils'
+import type { PropType } from '@vue/runtime-core'
 
 
 const props = defineProps({
 const props = defineProps({
   modelValue: {
   modelValue: {
     type: Array as PropType<Array<Date> | null>,
     type: Array as PropType<Array<Date> | null>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   maxHeight: {
   maxHeight: {
     type: Number,
     type: Number,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
 const emit = defineEmits(['update:modelValue'])
 const emit = defineEmits(['update:modelValue'])
@@ -51,13 +51,16 @@ const updateDateTimeRange = (value: [string, string]) => {
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const fnsLocale = DateUtils.getFnsLocale(i18n.locale.value as supportedLocales)
 const fnsLocale = DateUtils.getFnsLocale(i18n.locale.value as supportedLocales)
-const dateFormatPattern = DateUtils.getShortFormatPattern(i18n.locale.value as supportedLocales)
+const dateFormatPattern = DateUtils.getShortFormatPattern(
+  i18n.locale.value as supportedLocales,
+)
 
 
 const today = new Date()
 const today = new Date()
 
 
-let style = '';
+let style = ''
 if (props.maxHeight !== null) {
 if (props.maxHeight !== null) {
-  style += 'height: ' + props.maxHeight + 'px;max-height: ' + props.maxHeight + 'px;'
+  style +=
+    'height: ' + props.maxHeight + 'px;max-height: ' + props.maxHeight + 'px;'
 }
 }
 
 
 /**
 /**
@@ -70,57 +73,57 @@ const dateRangePickerAltPosition = (el: HTMLElement) => {
   const rightPadding = 30
   const rightPadding = 30
   const rect = el.getBoundingClientRect()
   const rect = el.getBoundingClientRect()
 
 
-  if ((rect.left + fullWidth + rightPadding) > window.innerWidth) {
+  if (rect.left + fullWidth + rightPadding > window.innerWidth) {
     xOffset = window.innerWidth - (rect.left + fullWidth + rightPadding)
     xOffset = window.innerWidth - (rect.left + fullWidth + rightPadding)
   }
   }
 
 
   return {
   return {
     top: rect.bottom,
     top: rect.bottom,
-    left: rect.left + xOffset
+    left: rect.left + xOffset,
   }
   }
 }
 }
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">
+// @see https://vue3datepicker.com/customization/theming/
+// [!] Sass variables overriding does not work in scoped mode
+.dp__theme_light,
+.dp__theme_dark {
+  --dp-background-color: #ffffff;
+  --dp-text-color: #212121;
+  --dp-hover-color: #f3f3f3;
+  --dp-hover-text-color: #212121;
+  --dp-hover-icon-color: #959595;
+  --dp-primary-color: rgb(var(--v-theme-primary)) !important;
+  --dp-primary-text-color: rgb(var(--v-theme-on-primary)) !important;
+  --dp-secondary-color: rgb(var(--v-theme-neutral-strong)) !important;
+  --dp-border-color: #ddd;
+  --dp-menu-border-color: #ddd;
+  --dp-border-color-hover: #aaaeb7;
+  --dp-disabled-color: #f6f6f6;
+  --dp-scroll-bar-background: #f3f3f3;
+  --dp-scroll-bar-color: #959595;
+  --dp-success-color: rgb(var(--v-theme-success)) !important;
+  --dp-success-color-disabled: rgb(var(--v-theme-neutral-strong)) !important;
+  --dp-icon-color: #959595;
+  --dp-danger-color: #ff6f60;
+  --dp-highlight-color: rgba(25, 118, 210, 0.1);
+}
 
 
-  // @see https://vue3datepicker.com/customization/theming/
-  // [!] Sass variables overriding does not work in scoped mode
-  .dp__theme_light, .dp__theme_dark {
-    --dp-background-color: #ffffff;
-    --dp-text-color: #212121;
-    --dp-hover-color: #f3f3f3;
-    --dp-hover-text-color: #212121;
-    --dp-hover-icon-color: #959595;
-    --dp-primary-color: rgb(var(--v-theme-primary)) !important;
-    --dp-primary-text-color: rgb(var(--v-theme-on-primary)) !important;
-    --dp-secondary-color: rgb(var(--v-theme-neutral-strong)) !important;
-    --dp-border-color: #ddd;
-    --dp-menu-border-color: #ddd;
-    --dp-border-color-hover: #aaaeb7;
-    --dp-disabled-color: #f6f6f6;
-    --dp-scroll-bar-background: #f3f3f3;
-    --dp-scroll-bar-color: #959595;
-    --dp-success-color: rgb(var(--v-theme-success)) !important;
-    --dp-success-color-disabled: rgb(var(--v-theme-neutral-strong)) !important;
-    --dp-icon-color: #959595;
-    --dp-danger-color: #ff6f60;
-    --dp-highlight-color: rgba(25, 118, 210, 0.1);
+.date-range-picker {
+  div {
+    height: 100% !important;
+    max-height: 100% !important;
   }
   }
 
 
-  .date-range-picker {
-    div {
-      height: 100% !important;
-      max-height: 100% !important;
-    }
-
-    .dp__input_wrap {
-      height: 100% !important;
-      max-height: 100% !important;
-    }
-
-    .date-range-picker-input {
-      height: 100% !important;
-      max-height: 100% !important;
-    }
+  .dp__input_wrap {
+    height: 100% !important;
+    max-height: 100% !important;
   }
   }
+
+  .date-range-picker-input {
+    height: 100% !important;
+    max-height: 100% !important;
+  }
+}
 </style>
 </style>

+ 30 - 30
components/Ui/ExpansionPanel.vue

@@ -25,47 +25,47 @@ Panneaux déroulants de type "accordéon"
 const props = defineProps({
 const props = defineProps({
   title: {
   title: {
     type: String,
     type: String,
-    required: true
+    required: true,
   },
   },
   icon: {
   icon: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-  .icon {
-    width: 47px;
-    height: 47px;
-    padding: 10px;
-    margin-right: 10px;
-    flex: none !important;
-  }
+.icon {
+  width: 47px;
+  height: 47px;
+  padding: 10px;
+  margin-right: 10px;
+  flex: none !important;
+}
 
 
-  .v-expansion-panel-header {
-    padding: 0;
-    padding-right: 20px;
-  }
+.v-expansion-panel-header {
+  padding: 0;
+  padding-right: 20px;
+}
 
 
-  .v-expansion-panel-title {
-    padding-left: 0;
-    padding-top: 0;
-    padding-bottom: 0;
-    max-height: 47px;
-    min-height: 47px;
-  }
+.v-expansion-panel-title {
+  padding-left: 0;
+  padding-top: 0;
+  padding-bottom: 0;
+  max-height: 47px;
+  min-height: 47px;
+}
 
 
-  .v-expansion-panel--active > .v-expansion-panel-title {
-    min-height: 47px !important;
-  }
+.v-expansion-panel--active > .v-expansion-panel-title {
+  min-height: 47px !important;
+}
 
 
-  :deep(.v-expansion-panel-title__icon > .v-icon) {
-    font-size: 16px;
-  }
+:deep(.v-expansion-panel-title__icon > .v-icon) {
+  font-size: 16px;
+}
 
 
-  .icon {
-    text-align: center;
-  }
+.icon {
+  text-align: center;
+}
 </style>
 </style>

+ 35 - 35
components/Ui/Form.vue

@@ -10,20 +10,20 @@ de quitter si des données ont été modifiées.
 <template>
 <template>
   <LayoutContainer>
   <LayoutContainer>
     <v-form
     <v-form
-        v-model="isValid"
-        ref="form"
-        :readonly="readonly"
-        @submit.prevent=""
+      v-model="isValid"
+      ref="form"
+      :readonly="readonly"
+      @submit.prevent=""
     >
     >
       <!-- Top action bar -->
       <!-- Top action bar -->
       <v-container
       <v-container
-          v-if="actionPosition === 'both' || actionPosition === 'top'"
-          :fluid="true"
-          class="container btnActions"
+        v-if="actionPosition === 'both' || actionPosition === 'top'"
+        :fluid="true"
+        class="container btnActions"
       >
       >
         <v-row>
         <v-row>
           <v-col cols="12" sm="12">
           <v-col cols="12" sm="12">
-            <slot name="form.button"/>
+            <slot name="form.button" />
 
 
             <UiButtonSubmit
             <UiButtonSubmit
               v-if="!readonly"
               v-if="!readonly"
@@ -36,17 +36,17 @@ de quitter si des données ont été modifiées.
       </v-container>
       </v-container>
 
 
       <!-- Content -->
       <!-- Content -->
-      <slot v-bind="{model, entity}"/>
+      <slot v-bind="{ model, entity }" />
 
 
       <!-- Bottom action bar -->
       <!-- Bottom action bar -->
       <v-container
       <v-container
-          v-if="actionPosition === 'both' || actionPosition === 'bottom'"
-          :fluid="true"
-          class="container btnActions mt-6"
+        v-if="actionPosition === 'both' || actionPosition === 'bottom'"
+        :fluid="true"
+        class="container btnActions mt-6"
       >
       >
         <v-row>
         <v-row>
           <v-col cols="12" sm="12">
           <v-col cols="12" sm="12">
-            <slot name="form.button"/>
+            <slot name="form.button" />
 
 
             <UiButtonSubmit
             <UiButtonSubmit
               @submit="submit"
               @submit="submit"
@@ -68,7 +68,7 @@ de quitter si des données ont été modifiées.
           {{ $t('caution') }}
           {{ $t('caution') }}
         </v-card-title>
         </v-card-title>
         <v-card-text>
         <v-card-text>
-          <br>
+          <br />
           <p>{{ $t('quit_without_saving_warning') }}.</p>
           <p>{{ $t('quit_without_saving_warning') }}.</p>
         </v-card-text>
         </v-card-text>
       </template>
       </template>
@@ -89,7 +89,6 @@ de quitter si des données ont été modifiées.
         </div>
         </div>
       </template>
       </template>
     </LazyLayoutDialog>
     </LazyLayoutDialog>
-
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 
@@ -106,7 +105,7 @@ import {watch} from "@vue/runtime-core";
 import type {PropType} from "@vue/runtime-core";
 import type {PropType} from "@vue/runtime-core";
 import type {AnyJson} from "~/types/data";
 import type {AnyJson} from "~/types/data";
 import * as _ from 'lodash-es'
 import * as _ from 'lodash-es'
-import {useRefreshProfile} from "~/composables/data/useRefreshProfile";
+import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -114,26 +113,26 @@ const props = defineProps({
    */
    */
   model: {
   model: {
     type: Function as any as () => typeof ApiModel,
     type: Function as any as () => typeof ApiModel,
-    required: true
+    required: true,
   },
   },
   /**
   /**
    * Instance de l'objet
    * Instance de l'objet
    */
    */
   entity: {
   entity: {
     type: Object as () => ApiModel,
     type: Object as () => ApiModel,
-    required: true
+    required: true,
   },
   },
   /**
   /**
    * TODO: compléter
    * TODO: compléter
    */
    */
   onChanged: {
   onChanged: {
     type: Function,
     type: Function,
-    required: false
+    required: false,
   },
   },
   goBackRoute: {
   goBackRoute: {
     type: Object as PropType<RouteLocationRaw>,
     type: Object as PropType<RouteLocationRaw>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Types de soumission disponibles (enregistrer / enregistrer et quitter)
    * Types de soumission disponibles (enregistrer / enregistrer et quitter)
@@ -145,7 +144,7 @@ const props = defineProps({
       let actions: AnyJson = {}
       let actions: AnyJson = {}
       actions[SUBMIT_TYPE.SAVE] = {}
       actions[SUBMIT_TYPE.SAVE] = {}
       return actions
       return actions
-    }
+    },
   },
   },
   /**
   /**
    * La validation est en cours
    * La validation est en cours
@@ -153,7 +152,7 @@ const props = defineProps({
   validationPending: {
   validationPending: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Faut-il rafraichir le profil à la soumission du formulaire?
    * Faut-il rafraichir le profil à la soumission du formulaire?
@@ -161,13 +160,13 @@ const props = defineProps({
   refreshProfile: {
   refreshProfile: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   actionPosition: {
   actionPosition: {
     type: String as PropType<'top' | 'bottom' | 'both'>,
     type: String as PropType<'top' | 'bottom' | 'both'>,
     required: false,
     required: false,
-    default: 'both'
-  }
+    default: 'both',
+  },
 })
 })
 
 
 // ### Définitions
 // ### Définitions
@@ -220,7 +219,7 @@ const closeConfirmationDialog = () => {
  *
  *
  * @param next
  * @param next
  */
  */
-const submit = async (next: string|null = null) => {
+const submit = async (next: string | null = null) => {
   if (props.validationPending) {
   if (props.validationPending) {
     return
     return
   }
   }
@@ -255,18 +254,21 @@ const submit = async (next: string|null = null) => {
     } else if (next === SUBMIT_TYPE.SAVE_AND_BACK) {
     } else if (next === SUBMIT_TYPE.SAVE_AND_BACK) {
       onSaveAndQuitAction(actionArgs)
       onSaveAndQuitAction(actionArgs)
     }
     }
-
   } catch (error: any) {
   } catch (error: any) {
-
-    if (error.response && error.response.status === 422 && error.response.data['violations']) {
-
+    if (
+      error.response &&
+      error.response.status === 422 &&
+      error.response.data['violations']
+    ) {
       // TODO: à revoir
       // TODO: à revoir
       const violations: Array<string> = []
       const violations: Array<string> = []
       let fields: AnyJson = {}
       let fields: AnyJson = {}
 
 
       for (const violation of error.response.data['violations']) {
       for (const violation of error.response.data['violations']) {
         violations.push(i18n.t(violation['message']) as string)
         violations.push(i18n.t(violation['message']) as string)
-        fields = Object.assign(fields, {[violation['propertyPath']] : violation['message']})
+        fields = Object.assign(fields, {
+          [violation['propertyPath']]: violation['message'],
+        })
       }
       }
 
 
       formStore.addViolation(fields)
       formStore.addViolation(fields)
@@ -297,7 +299,7 @@ const saveAndQuit = async () => {
  * @param route
  * @param route
  * @param id
  * @param id
  */
  */
-function onSaveAction(route: Route, id: number){
+function onSaveAction(route: Route, id: number) {
   if (formStore.formFunction === FORM_FUNCTION.CREATE) {
   if (formStore.formFunction === FORM_FUNCTION.CREATE) {
     route.path += id
     route.path += id
     navigateTo(route)
     navigateTo(route)
@@ -352,7 +354,7 @@ const cancel = () => {
   }
   }
 }
 }
 
 
-const actions = computed(()=>{
+const actions = computed(() => {
   return _.keys(props.submitActions)
   return _.keys(props.submitActions)
 })
 })
 
 
@@ -384,7 +386,6 @@ const validate = async function () {
   errors.value = validation.errors
   errors.value = validation.errors
 }
 }
 
 
-
 // #### Gestion de l'état dirty
 // #### Gestion de l'état dirty
 watch(props.entity, async (newEntity, oldEntity) => {
 watch(props.entity, async (newEntity, oldEntity) => {
   setIsDirty(true)
   setIsDirty(true)
@@ -410,7 +411,6 @@ const setIsDirty = (dirty: boolean) => {
 }
 }
 
 
 defineExpose({ validate })
 defineExpose({ validate })
-
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>

+ 13 - 21
components/Ui/Form/Creation.vue

@@ -1,27 +1,22 @@
 <template>
 <template>
-  <UiForm
-      :model="model"
-      :entity="entity"
-      :submitActions="submitActions"
-  >
+  <UiForm :model="model" :entity="entity" :submitActions="submitActions">
     <template #form.button>
     <template #form.button>
       <v-btn v-if="goBackRoute" class="theme-neutral mr-3" @click="quit">
       <v-btn v-if="goBackRoute" class="theme-neutral mr-3" @click="quit">
         {{ $t('cancel') }}
         {{ $t('cancel') }}
       </v-btn>
       </v-btn>
     </template>
     </template>
 
 
-    <slot v-bind="{model, entity}"/>
+    <slot v-bind="{ model, entity }" />
   </UiForm>
   </UiForm>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import type {PropType} from "@vue/runtime-core";
-import type {RouteLocationRaw} from "@intlify/vue-router-bridge";
-import ApiModel from "~/models/ApiModel";
-import type {AnyJson} from "~/types/data";
-import {SUBMIT_TYPE} from "~/types/enum/enums";
-import {useEntityManager} from "~/composables/data/useEntityManager";
+import type { PropType } from '@vue/runtime-core'
+import type { RouteLocationRaw } from '@intlify/vue-router-bridge'
+import ApiModel from '~/models/ApiModel'
+import type { AnyJson } from '~/types/data'
+import { SUBMIT_TYPE } from '~/types/enum/enums'
+import { useEntityManager } from '~/composables/data/useEntityManager'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -29,7 +24,7 @@ const props = defineProps({
    */
    */
   model: {
   model: {
     type: Function as any as () => typeof ApiModel,
     type: Function as any as () => typeof ApiModel,
-    required: true
+    required: true,
   },
   },
   /**
   /**
    * Route de retour
    * Route de retour
@@ -37,7 +32,7 @@ const props = defineProps({
   goBackRoute: {
   goBackRoute: {
     type: Object as PropType<RouteLocationRaw>,
     type: Object as PropType<RouteLocationRaw>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * La validation est en cours
    * La validation est en cours
@@ -45,7 +40,7 @@ const props = defineProps({
   validationPending: {
   validationPending: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Faut-il rafraichir le profil à la soumission du formulaire ?
    * Faut-il rafraichir le profil à la soumission du formulaire ?
@@ -53,7 +48,7 @@ const props = defineProps({
   refreshProfile: {
   refreshProfile: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
 })
 })
 
 
@@ -81,9 +76,6 @@ const quit = () => {
 
 
   router.push(props.goBackRoute)
   router.push(props.goBackRoute)
 }
 }
-
 </script>
 </script>
 
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 22 - 28
components/Ui/Form/Edition.vue

@@ -2,10 +2,10 @@
   <LayoutContainer>
   <LayoutContainer>
     <UiLoadingPanel v-if="pending" />
     <UiLoadingPanel v-if="pending" />
     <UiForm
     <UiForm
-        v-else
-        :model="model"
-        :entity="entity"
-        :submitActions="submitActions"
+      v-else
+      :model="model"
+      :entity="entity"
+      :submitActions="submitActions"
     >
     >
       <template #form.button>
       <template #form.button>
         <v-btn v-if="goBackRoute" class="theme-neutral mr-3" @click="quit">
         <v-btn v-if="goBackRoute" class="theme-neutral mr-3" @click="quit">
@@ -13,20 +13,19 @@
         </v-btn>
         </v-btn>
       </template>
       </template>
 
 
-      <slot v-bind="{model, entity}"/>
+      <slot v-bind="{ model, entity }" />
     </UiForm>
     </UiForm>
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import type {PropType} from "@vue/runtime-core";
-import type {RouteLocationRaw} from "@intlify/vue-router-bridge";
-import ApiModel from "~/models/ApiModel";
-import type {AnyJson} from "~/types/data";
-import {SUBMIT_TYPE} from "~/types/enum/enums";
-import {useRoute} from "vue-router";
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
+import type { PropType } from '@vue/runtime-core'
+import type { RouteLocationRaw } from '@intlify/vue-router-bridge'
+import ApiModel from '~/models/ApiModel'
+import type { AnyJson } from '~/types/data'
+import { SUBMIT_TYPE } from '~/types/enum/enums'
+import { useRoute } from 'vue-router'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -34,7 +33,7 @@ const props = defineProps({
    */
    */
   model: {
   model: {
     type: Function as any as () => typeof ApiModel,
     type: Function as any as () => typeof ApiModel,
-    required: true
+    required: true,
   },
   },
   /**
   /**
    * Id de l'objet
    * Id de l'objet
@@ -43,7 +42,7 @@ const props = defineProps({
   id: {
   id: {
     type: Number,
     type: Number,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Route de retour
    * Route de retour
@@ -51,7 +50,7 @@ const props = defineProps({
   goBackRoute: {
   goBackRoute: {
     type: Object as PropType<RouteLocationRaw>,
     type: Object as PropType<RouteLocationRaw>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * La validation est en cours
    * La validation est en cours
@@ -59,7 +58,7 @@ const props = defineProps({
   validationPending: {
   validationPending: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Faut-il rafraichir le profil à la soumission du formulaire ?
    * Faut-il rafraichir le profil à la soumission du formulaire ?
@@ -67,20 +66,18 @@ const props = defineProps({
   refreshProfile: {
   refreshProfile: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
-  }
+    default: false,
+  },
 })
 })
 
 
 const { fetch } = useEntityFetch()
 const { fetch } = useEntityFetch()
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
 
 
-const entityId = props.id !== null ? props.id : parseInt(route.params.id as string)
+const entityId =
+  props.id !== null ? props.id : parseInt(route.params.id as string)
 
 
-const { data: entity, pending } = fetch(
-    props.model,
-    entityId
-)
+const { data: entity, pending } = fetch(props.model, entityId)
 
 
 const submitActions = computed(() => {
 const submitActions = computed(() => {
   let actions: AnyJson = {}
   let actions: AnyJson = {}
@@ -100,9 +97,6 @@ const quit = () => {
 
 
   router.push(props.goBackRoute)
   router.push(props.goBackRoute)
 }
 }
-
 </script>
 </script>
 
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 10 - 8
components/Ui/Help.vue

@@ -29,34 +29,34 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import type {Ref} from "@vue/reactivity";
+import type { Ref } from '@vue/reactivity'
 
 
 const props = defineProps({
 const props = defineProps({
   left: {
   left: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   right: {
   right: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   top: {
   top: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   bottom: {
   bottom: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   icon: {
   icon: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: 'mdi-help-circle'
-  }
+    default: 'mdi-help-circle',
+  },
 })
 })
 
 
 const { $refs } = useNuxtApp()
 const { $refs } = useNuxtApp()
@@ -67,7 +67,9 @@ const show: Ref<Boolean> = ref(false)
 const iconRef = ref(null)
 const iconRef = ref(null)
 
 
 // Left is the default, set it to true if not any other is true
 // Left is the default, set it to true if not any other is true
-const leftOrDefault: Ref<Boolean> = ref(props.left || (!props.right && !props.bottom && !props.top))
+const leftOrDefault: Ref<Boolean> = ref(
+  props.left || (!props.right && !props.bottom && !props.top),
+)
 
 
 const onIconClicked = (e: any) => {
 const onIconClicked = (e: any) => {
   show.value = !show.value
   show.value = !show.value

+ 58 - 50
components/Ui/Image.vue

@@ -4,7 +4,7 @@ Permet d'afficher une image par défaut si l'image demandée n'est pas disponibl
 -->
 -->
 <template>
 <template>
   <main>
   <main>
-    <div class="image-wrapper" :style="{width: width + 'px'}">
+    <div class="image-wrapper" :style="{ width: width + 'px' }">
       <v-img
       <v-img
         :src="imageSrc ?? undefined"
         :src="imageSrc ?? undefined"
         :lazy-src="defaultImagePath"
         :lazy-src="defaultImagePath"
@@ -19,14 +19,15 @@ Permet d'afficher une image par défaut si l'image demandée n'est pas disponibl
             justify="center"
             justify="center"
             v-if="pending"
             v-if="pending"
           >
           >
-            <v-progress-circular
-              :indeterminate="true"
-              color="neutral"
-            />
+            <v-progress-circular :indeterminate="true" color="neutral" />
           </v-row>
           </v-row>
         </template>
         </template>
 
 
-        <div v-if="!pending && overlayIcon" class="overlay" @click="emit('overlay-clicked')">
+        <div
+          v-if="!pending && overlayIcon"
+          class="overlay"
+          @click="emit('overlay-clicked')"
+        >
           <v-icon>{{ overlayIcon }}</v-icon>
           <v-icon>{{ overlayIcon }}</v-icon>
         </div>
         </div>
       </v-img>
       </v-img>
@@ -35,10 +36,10 @@ Permet d'afficher une image par défaut si l'image demandée n'est pas disponibl
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useImageFetch} from "~/composables/data/useImageFetch";
-import ImageManager from "~/services/data/imageManager";
-import type {WatchStopHandle} from "@vue/runtime-core";
-import type {Ref} from "@vue/reactivity";
+import { useImageFetch } from '~/composables/data/useImageFetch'
+import ImageManager from '~/services/data/imageManager'
+import type { WatchStopHandle } from '@vue/runtime-core'
+import type { Ref } from '@vue/reactivity'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -47,28 +48,28 @@ const props = defineProps({
   imageId: {
   imageId: {
     type: Number as PropType<number | null>,
     type: Number as PropType<number | null>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Image par défaut
    * Image par défaut
    */
    */
   defaultImage: {
   defaultImage: {
     type: String,
     type: String,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Hauteur de l'image à l'écran (en px)
    * Hauteur de l'image à l'écran (en px)
    */
    */
   height: {
   height: {
     type: Number,
     type: Number,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Largeur de l'image à l'écran (en px)
    * Largeur de l'image à l'écran (en px)
    */
    */
   width: {
   width: {
     type: Number,
     type: Number,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Icône à afficher en overlay au survol de la souris
    * Icône à afficher en overlay au survol de la souris
@@ -76,8 +77,8 @@ const props = defineProps({
   overlayIcon: {
   overlayIcon: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
 const { fetch } = useImageFetch()
 const { fetch } = useImageFetch()
@@ -88,7 +89,11 @@ const emit = defineEmits(['overlay-clicked'])
 
 
 const fileId = toRef(props, 'imageId')
 const fileId = toRef(props, 'imageId')
 
 
-const { data: imageSrc, pending, refresh: refreshImage } = await fetch(fileId, defaultImagePath, props.height, props.width) as any
+const {
+  data: imageSrc,
+  pending,
+  refresh: refreshImage,
+} = (await fetch(fileId, defaultImagePath, props.height, props.width)) as any
 
 
 const refresh = () => {
 const refresh = () => {
   refreshImage()
   refreshImage()
@@ -98,9 +103,12 @@ defineExpose({ refresh })
 /**
 /**
  * Si l'id change, on recharge l'image
  * Si l'id change, on recharge l'image
  */
  */
-const unwatch: WatchStopHandle = watch(() => props.imageId, async (value, oldValue) => {
-  refresh()
-})
+const unwatch: WatchStopHandle = watch(
+  () => props.imageId,
+  async (value, oldValue) => {
+    refresh()
+  },
+)
 
 
 /**
 /**
  * Lorsqu'on démonte le component, on supprime le watcher
  * Lorsqu'on démonte le component, on supprime le watcher
@@ -111,38 +119,38 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">
-  div.image-wrapper {
-    display: block;
-    position: relative;
+div.image-wrapper {
+  display: block;
+  position: relative;
 
 
-    img {
-      display: block;
-      max-width: 100%;
-    }
+  img {
+    display: block;
+    max-width: 100%;
+  }
 
 
-    .overlay {
-      position: absolute;
-      top: 0;
-      bottom: 0;
-      left: 0;
-      right: 0;
-      height: 100%;
-      width: 100%;
-      opacity: 0;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      transition: .3s ease;
-    }
-    .overlay:hover {
-      opacity: 0.8;
-      background-color: rgb(var(--v-theme-neutral-strong));
-      cursor: pointer;
-    }
+  .overlay {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 100%;
+    width: 100%;
+    opacity: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: 0.3s ease;
+  }
+  .overlay:hover {
+    opacity: 0.8;
+    background-color: rgb(var(--v-theme-neutral-strong));
+    cursor: pointer;
+  }
 
 
-    .overlay .v-icon {
-      color: rgb(var(--v-theme-on-neutral-strong));
-      font-size: 36px;
-    }
+  .overlay .v-icon {
+    color: rgb(var(--v-theme-on-neutral-strong));
+    font-size: 36px;
   }
   }
+}
 </style>
 </style>

+ 92 - 67
components/Ui/Input/Autocomplete.vue

@@ -8,45 +8,49 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
   <main>
   <main>
     <!--suppress TypeScriptValidateTypes -->
     <!--suppress TypeScriptValidateTypes -->
     <v-autocomplete
     <v-autocomplete
-        :model-value="modelValue"
-        autocomplete="search"
-        :items="items"
-        :label="$t(fieldLabel)"
-        :item-title="itemTitle"
-        :item-value="itemValue"
-        :no-filter="noFilter"
-        :auto-select-first="autoSelectFirst"
-        :multiple="multiple"
-        :loading="isLoading"
-        :return-object="returnObject"
-        :search-input.sync="search"
-        :prepend-icon="prependIcon"
-        :error="error || !!fieldViolations"
-        :error-messages="errorMessage || fieldViolations ? $t(fieldViolations) : ''"
-        :rules="rules"
-        :chips="chips"
-        :hide-no-data="hideNoData"
-        :no-data-text="isLoading ? $t('please_wait') : $t('no_result_matching_your_request')"
-        :variant="variant"
-        @update:model-value="onUpdate"
-        @update:search="emit('update:search', $event)"
-        @update:menu="emit('update:menu', $event)"
-        @update:focused="emit('update:focused', $event)"
+      :model-value="modelValue"
+      autocomplete="search"
+      :items="items"
+      :label="$t(fieldLabel)"
+      :item-title="itemTitle"
+      :item-value="itemValue"
+      :no-filter="noFilter"
+      :auto-select-first="autoSelectFirst"
+      :multiple="multiple"
+      :loading="isLoading"
+      :return-object="returnObject"
+      :search-input.sync="search"
+      :prepend-icon="prependIcon"
+      :error="error || !!fieldViolations"
+      :error-messages="
+        errorMessage || fieldViolations ? $t(fieldViolations) : ''
+      "
+      :rules="rules"
+      :chips="chips"
+      :hide-no-data="hideNoData"
+      :no-data-text="
+        isLoading ? $t('please_wait') : $t('no_result_matching_your_request')
+      "
+      :variant="variant"
+      @update:model-value="onUpdate"
+      @update:search="emit('update:search', $event)"
+      @update:menu="emit('update:menu', $event)"
+      @update:focused="emit('update:focused', $event)"
     >
     >
       <template v-if="slotText" #item="data">
       <template v-if="slotText" #item="data">
-<!--        <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>-->
+        <!--        <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>-->
       </template>
       </template>
     </v-autocomplete>
     </v-autocomplete>
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {computed} from "@vue/reactivity";
-import type {ComputedRef, Ref} from "@vue/reactivity";
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
-import ObjectUtils from "~/services/utils/objectUtils";
-import type {AnyJson} from "~/types/data";
-import type {PropType} from "@vue/runtime-core";
+import { computed } from '@vue/reactivity'
+import type { ComputedRef, Ref } from '@vue/reactivity'
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
+import ObjectUtils from '~/services/utils/objectUtils'
+import type { AnyJson } from '~/types/data'
+import type { PropType } from '@vue/runtime-core'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -55,7 +59,7 @@ const props = defineProps({
   modelValue: {
   modelValue: {
     type: [String, Number, Object, Array] as PropType<any>,
     type: [String, Number, Object, Array] as PropType<any>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -65,7 +69,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -74,7 +78,7 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Liste des éléments de la liste
    * Liste des éléments de la liste
@@ -83,7 +87,7 @@ const props = defineProps({
   items: {
   items: {
     type: Array as PropType<Array<Object>>,
     type: Array as PropType<Array<Object>>,
     required: false,
     required: false,
-    default: () => []
+    default: () => [],
   },
   },
   /**
   /**
    * Définit si le champ est en lecture seule
    * Définit si le champ est en lecture seule
@@ -91,7 +95,7 @@ const props = defineProps({
    */
    */
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Le model est l'objet lui-même, et non pas son id (ou la propriété définie avec itemValue)
    * Le model est l'objet lui-même, et non pas son id (ou la propriété définie avec itemValue)
@@ -99,7 +103,7 @@ const props = defineProps({
    */
    */
   returnObject: {
   returnObject: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Autorise la sélection multiple
    * Autorise la sélection multiple
@@ -107,7 +111,7 @@ const props = defineProps({
    */
    */
   multiple: {
   multiple: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Propriété de l'objet à utiliser comme label
    * Propriété de l'objet à utiliser comme label
@@ -115,7 +119,7 @@ const props = defineProps({
    */
    */
   itemTitle: {
   itemTitle: {
     type: String,
     type: String,
-    default: 'title'
+    default: 'title',
   },
   },
   /**
   /**
    * Propriété de l'objet à utiliser comme clé (et correspondant au v-model)
    * Propriété de l'objet à utiliser comme clé (et correspondant au v-model)
@@ -123,14 +127,14 @@ const props = defineProps({
    */
    */
   itemValue: {
   itemValue: {
     type: String,
     type: String,
-    default: 'id'
+    default: 'id',
   },
   },
   /**
   /**
    * Icône de gauche
    * Icône de gauche
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-prepend-icon
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-prepend-icon
    */
    */
   prependIcon: {
   prependIcon: {
-    type: String
+    type: String,
   },
   },
   /**
   /**
    * Rends les résultats sous forme de puces
    * Rends les résultats sous forme de puces
@@ -138,7 +142,7 @@ const props = defineProps({
    */
    */
   chips: {
   chips: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Le contenu de la liste est en cours de chargement
    * Le contenu de la liste est en cours de chargement
@@ -146,7 +150,7 @@ const props = defineProps({
   isLoading: {
   isLoading: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Propriété de l'objet utilisé pour grouper les items ; laisser null pour ne pas grouper
    * Propriété de l'objet utilisé pour grouper les items ; laisser null pour ne pas grouper
@@ -154,7 +158,7 @@ const props = defineProps({
   group: {
   group: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-hide-no-data
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-hide-no-data
@@ -162,32 +166,32 @@ const props = defineProps({
   hideNoData: {
   hideNoData: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   // TODO: c'est quoi?
   // TODO: c'est quoi?
   slotText: {
   slotText: {
     type: Array,
     type: Array,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-no-filter
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-no-filter
    */
    */
   noFilter: {
   noFilter: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-auto-select-first
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-auto-select-first
    */
    */
   autoSelectFirst: {
   autoSelectFirst: {
     type: Boolean,
     type: Boolean,
-    default: true
+    default: true,
   },
   },
   // TODO: c'est quoi?
   // TODO: c'est quoi?
   translate: {
   translate: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Règles de validation
    * Règles de validation
@@ -196,14 +200,14 @@ const props = defineProps({
   rules: {
   rules: {
     type: Array,
     type: Array,
     required: false,
     required: false,
-    default: () => []
+    default: () => [],
   },
   },
   /**
   /**
    * Le champ est-il actuellement en état d'erreur
    * Le champ est-il actuellement en état d'erreur
    */
    */
   error: {
   error: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Si le champ est en état d'erreur, quel est le message d'erreur?
    * Si le champ est en état d'erreur, quel est le message d'erreur?
@@ -211,27 +215,41 @@ const props = defineProps({
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    */
    */
   variant: {
   variant: {
-    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    type: String as PropType<
+      | 'filled'
+      | 'outlined'
+      | 'plain'
+      | 'underlined'
+      | 'solo'
+      | 'solo-inverted'
+      | 'solo-filled'
+      | undefined
+    >,
     required: false,
     required: false,
-    default: 'filled'
-  }
+    default: 'filled',
+  },
 })
 })
 
 
 const i18n = useI18n()
 const i18n = useI18n()
 
 
-const search: Ref<string|null> = ref(null)
+const search: Ref<string | null> = ref(null)
 
 
 const fieldLabel: string = props.label ?? props.field
 const fieldLabel: string = props.label ?? props.field
 
 
-const {fieldViolations, updateViolationState} = useFieldViolation(props.field)
+const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
 
 
-const emit = defineEmits(['update:model-value', 'update:search', 'update:focused', 'update:menu'])
+const emit = defineEmits([
+  'update:model-value',
+  'update:search',
+  'update:focused',
+  'update:menu',
+])
 
 
 const onUpdate = (event: string) => {
 const onUpdate = (event: string) => {
   updateViolationState(event)
   updateViolationState(event)
@@ -292,16 +310,17 @@ const prepareGroups = (groupedItems: Array<Array<string>>): Array<AnyJson> => {
   let finalItems: Array<AnyJson> = []
   let finalItems: Array<AnyJson> = []
 
 
   for (const group in groupedItems) {
   for (const group in groupedItems) {
-
     // Si un groupe est présent, alors on créé le groupe options header
     // Si un groupe est présent, alors on créé le groupe options header
     if (group !== 'undefined') {
     if (group !== 'undefined') {
-      finalItems.push({header: i18n.t(group as string)})
+      finalItems.push({ header: i18n.t(group as string) })
     }
     }
 
 
     // On parcourt les items pour préparer les texts / slotTexts à afficher
     // On parcourt les items pour préparer les texts / slotTexts à afficher
-    finalItems = finalItems.concat(groupedItems[group].map((item: any) => {
-      return prepareItem(item)
-    }))
+    finalItems = finalItems.concat(
+      groupedItems[group].map((item: any) => {
+        return prepareItem(item)
+      }),
+    )
   }
   }
   return finalItems
   return finalItems
 }
 }
@@ -321,16 +340,22 @@ const prepareItem = (item: Object): AnyJson => {
   // Si on souhaite avoir un texte différent dans les propositions que dans la sélection finale de select
   // Si on souhaite avoir un texte différent dans les propositions que dans la sélection finale de select
   if (props.slotText) {
   if (props.slotText) {
     for (const text of props.slotText) {
     for (const text of props.slotText) {
-      slotTextDisplay.push(props.translate ? i18n.t(item[text as string]) : item[text as string])
+      slotTextDisplay.push(
+        props.translate ? i18n.t(item[text as string]) : item[text as string],
+      )
     }
     }
   }
   }
 
 
   for (const text of props.itemTitle) {
   for (const text of props.itemTitle) {
-    itemTextDisplay.push(props.translate ? i18n.t(item[text as string]) : item[text as string])
+    itemTextDisplay.push(
+      props.translate ? i18n.t(item[text as string]) : item[text as string],
+    )
   }
   }
 
 
   // On reconstruit l'objet
   // On reconstruit l'objet
-  return Object.assign({}, item, { itemTextDisplay: itemTextDisplay.join(' '), slotTextDisplay: slotTextDisplay.join(' ') })
+  return Object.assign({}, item, {
+    itemTextDisplay: itemTextDisplay.join(' '),
+    slotTextDisplay: slotTextDisplay.join(' '),
+  })
 }
 }
-
 </script>
 </script>

+ 62 - 50
components/Ui/Input/Autocomplete/Accesses.vue

@@ -7,34 +7,34 @@ Champs autocomplete dédié à la recherche des access d'une structure
 <template>
 <template>
   <main>
   <main>
     <UiInputAutocomplete
     <UiInputAutocomplete
-        :model-value="modelValue"
-        :field="field"
-        :label="label"
-        :items="items"
-        item-value="id"
-        :isLoading="pending"
-        :multiple="multiple"
-        hide-no-data
-        :chips="chips"
-        :auto-select-first="false"
-        prependIcon="fas fa-magnifying-glass"
-        :return-object="false"
-        :variant="variant"
-        @update:model-value="onUpdateModelValue"
-        @update:search="onUpdateSearch"
+      :model-value="modelValue"
+      :field="field"
+      :label="label"
+      :items="items"
+      item-value="id"
+      :isLoading="pending"
+      :multiple="multiple"
+      hide-no-data
+      :chips="chips"
+      :auto-select-first="false"
+      prependIcon="fas fa-magnifying-glass"
+      :return-object="false"
+      :variant="variant"
+      @update:model-value="onUpdateModelValue"
+      @update:search="onUpdateSearch"
     />
     />
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import type {PropType} from "@vue/runtime-core";
-import {computed} from "@vue/reactivity";
-import type {ComputedRef, Ref} from "@vue/reactivity";
-import type {AnyJson, AssociativeArray} from "~/types/data";
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import Access from "~/models/Access/Access";
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import ArrayUtils from "~/services/utils/arrayUtils";
+import type { PropType } from '@vue/runtime-core'
+import { computed } from '@vue/reactivity'
+import type { ComputedRef, Ref } from '@vue/reactivity'
+import type { AnyJson, AssociativeArray } from '~/types/data'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import Access from '~/models/Access/Access'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import ArrayUtils from '~/services/utils/arrayUtils'
 import * as _ from 'lodash-es'
 import * as _ from 'lodash-es'
 
 
 const props = defineProps({
 const props = defineProps({
@@ -44,7 +44,7 @@ const props = defineProps({
   modelValue: {
   modelValue: {
     type: [Object, Array],
     type: [Object, Array],
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Filtres à transmettre à la source de données
    * Filtres à transmettre à la source de données
@@ -52,7 +52,7 @@ const props = defineProps({
   filters: {
   filters: {
     type: Object as PropType<Ref<AssociativeArray>>,
     type: Object as PropType<Ref<AssociativeArray>>,
     required: false,
     required: false,
-    default: ref(null)
+    default: ref(null),
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -62,7 +62,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -71,7 +71,7 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Définit si le champ est en lecture seule
    * Définit si le champ est en lecture seule
@@ -79,7 +79,7 @@ const props = defineProps({
    */
    */
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Autorise la sélection multiple
    * Autorise la sélection multiple
@@ -87,7 +87,7 @@ const props = defineProps({
    */
    */
   multiple: {
   multiple: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Rends les résultats sous forme de puces
    * Rends les résultats sous forme de puces
@@ -95,30 +95,39 @@ const props = defineProps({
    */
    */
   chips: {
   chips: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Closes the menu and clear the current search after the selection has been updated
    * Closes the menu and clear the current search after the selection has been updated
    */
    */
   clearSearchAfterUpdate: {
   clearSearchAfterUpdate: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    */
    */
   variant: {
   variant: {
-    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    type: String as PropType<
+      | 'filled'
+      | 'outlined'
+      | 'plain'
+      | 'underlined'
+      | 'solo'
+      | 'solo-inverted'
+      | 'solo-filled'
+      | undefined
+    >,
     required: false,
     required: false,
-    default: 'filled'
-  }
+    default: 'filled',
+  },
 })
 })
 
 
 /**
 /**
  * Element de la liste autocomplete
  * Element de la liste autocomplete
  */
  */
 interface AccessListItem {
 interface AccessListItem {
-  id: number | string,
+  id: number | string
   title: string
   title: string
 }
 }
 
 
@@ -133,7 +142,9 @@ const i18n = useI18n()
 const accessToItem = (access: Access): AccessListItem => {
 const accessToItem = (access: Access): AccessListItem => {
   return {
   return {
     id: access.id,
     id: access.id,
-    title: access.person ? `${access.person.name} ${access.person.givenName}` : i18n.t('unknown')
+    title: access.person
+      ? `${access.person.name} ${access.person.givenName}`
+      : i18n.t('unknown'),
   }
   }
 }
 }
 
 
@@ -148,7 +159,7 @@ const nameFilter: Ref<string | null> = ref(null)
  * Query transmise à l'API lors des changements de filtre de recherche
  * Query transmise à l'API lors des changements de filtre de recherche
  */
  */
 const query: ComputedRef<AnyJson> = computed(() => {
 const query: ComputedRef<AnyJson> = computed(() => {
-  let q: AnyJson = {'groups[]': 'access_people_ref', 'order[name]': 'asc'}
+  let q: AnyJson = { 'groups[]': 'access_people_ref', 'order[name]': 'asc' }
 
 
   if (!initialized.value && props.modelValue) {
   if (!initialized.value && props.modelValue) {
     if (Array.isArray(props.modelValue) && props.modelValue.length > 0) {
     if (Array.isArray(props.modelValue) && props.modelValue.length > 0) {
@@ -169,18 +180,17 @@ const query: ComputedRef<AnyJson> = computed(() => {
 /**
 /**
  * On commence par fetcher les accesses déjà actifs, pour affichage des noms
  * On commence par fetcher les accesses déjà actifs, pour affichage des noms
  */
  */
-const { data: collection, pending, refresh } = await fetchCollection(
-    Access,
-    null,
-    query
-)
+const {
+  data: collection,
+  pending,
+  refresh,
+} = await fetchCollection(Access, null, query)
 initialized.value = true
 initialized.value = true
 
 
 // On a déjà récupéré les access actifs, on relance une requête pour récupérer la première page
 // On a déjà récupéré les access actifs, on relance une requête pour récupérer la première page
 // des accesses suivants
 // des accesses suivants
 refresh()
 refresh()
 
 
-
 /**
 /**
  * Contenu de la liste autocomplete
  * Contenu de la liste autocomplete
  */
  */
@@ -200,7 +210,10 @@ const items: ComputedRef<Array<AccessListItem>> = computed(() => {
   fetchedItems.sort((a, b) => {
   fetchedItems.sort((a, b) => {
     if (props.modelValue.includes(a.id) && !props.modelValue.includes(b.id)) {
     if (props.modelValue.includes(a.id) && !props.modelValue.includes(b.id)) {
       return -1
       return -1
-    } else if (!props.modelValue.includes(a.id) && props.modelValue.includes(b.id)) {
+    } else if (
+      !props.modelValue.includes(a.id) &&
+      props.modelValue.includes(b.id)
+    ) {
       return 1
       return 1
     } else {
     } else {
       return a.title.localeCompare(b.title)
       return a.title.localeCompare(b.title)
@@ -210,7 +223,6 @@ const items: ComputedRef<Array<AccessListItem>> = computed(() => {
   return fetchedItems
   return fetchedItems
 })
 })
 
 
-
 /**
 /**
  * Délai entre le dernier caractère saisi et la requête de vérification de la mise à jour des résultats (en ms)
  * Délai entre le dernier caractère saisi et la requête de vérification de la mise à jour des résultats (en ms)
  */
  */
@@ -221,7 +233,7 @@ const inputDelay = 600
  * @see https://docs-lodash.com/v4/debounce/
  * @see https://docs-lodash.com/v4/debounce/
  */
  */
 const refreshDebounced: _.DebouncedFunc<() => void> = _.debounce(async () => {
 const refreshDebounced: _.DebouncedFunc<() => void> = _.debounce(async () => {
-  await refresh();
+  await refresh()
 }, inputDelay)
 }, inputDelay)
 
 
 // ### Events
 // ### Events
@@ -238,14 +250,14 @@ const onUpdateSearch = (event: string) => {
 
 
 const onUpdateModelValue = (event: Array<number>) => {
 const onUpdateModelValue = (event: Array<number>) => {
   if (props.clearSearchAfterUpdate) {
   if (props.clearSearchAfterUpdate) {
-    nameFilter.value = ""
+    nameFilter.value = ''
   }
   }
   emit('update:model-value', event)
   emit('update:model-value', event)
 }
 }
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  .v-autocomplete {
-    min-width: 350px;
-  }
+.v-autocomplete {
+  min-width: 350px;
+}
 </style>
 </style>

+ 44 - 45
components/Ui/Input/AutocompleteWithAPI.vue

@@ -7,95 +7,94 @@ d'une api)
 <template>
 <template>
   <main>
   <main>
     <UiInputAutocomplete
     <UiInputAutocomplete
-        :field="field"
-        :label="label"
-        :data="remoteData ? remoteData : data"
-        :items="items"
-        :isLoading="isLoading"
-        :item-text="itemText"
-        :slotText="slotText"
-        :item-value="itemValue"
-        :multiple="multiple"
-        :chips="chips"
-        prependIcon="mdi-magnify"
-        :return-object="returnObject"
-        @research="search"
-        :no-filter="noFilter"
-        @update="$emit('update', $event, field)"
+      :field="field"
+      :label="label"
+      :data="remoteData ? remoteData : data"
+      :items="items"
+      :isLoading="isLoading"
+      :item-text="itemText"
+      :slotText="slotText"
+      :item-value="itemValue"
+      :multiple="multiple"
+      :chips="chips"
+      prependIcon="mdi-magnify"
+      :return-object="returnObject"
+      @research="search"
+      :no-filter="noFilter"
+      @update="$emit('update', $event, field)"
     />
     />
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {ref, toRefs} from "@vue/reactivity";
-import type {Ref} from "@vue/reactivity";
-import UrlUtils from "~/services/utils/urlUtils";
-import {useFetch} from "#app";
-import {watch} from "@vue/runtime-core";
+import { ref, toRefs } from '@vue/reactivity'
+import type { Ref } from '@vue/reactivity'
+import UrlUtils from '~/services/utils/urlUtils'
+import { useFetch } from '#app'
+import { watch } from '@vue/runtime-core'
 
 
 const props = defineProps({
 const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   searchFunction: {
   searchFunction: {
     type: Function,
     type: Function,
-    required: true
+    required: true,
   },
   },
   data: {
   data: {
     type: [String, Number, Object, Array],
     type: [String, Number, Object, Array],
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   remoteUri: {
   remoteUri: {
     type: [Array],
     type: [Array],
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   remoteUrl: {
   remoteUrl: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   itemValue: {
   itemValue: {
     type: String,
     type: String,
-    default: 'id'
+    default: 'id',
   },
   },
   itemTitle: {
   itemTitle: {
     type: Array,
     type: Array,
-    required: true
+    required: true,
   },
   },
   slotText: {
   slotText: {
     type: Array,
     type: Array,
-    required: false
+    required: false,
   },
   },
   returnObject: {
   returnObject: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   noFilter: {
   noFilter: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   multiple: {
   multiple: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   chips: {
   chips: {
     type: Boolean,
     type: Boolean,
-    default: false
-  }
+    default: false,
+  },
 })
 })
 
 
 const { data } = toRefs(props)
 const { data } = toRefs(props)
@@ -103,19 +102,19 @@ const items = ref([])
 const remoteData: Ref<Array<string> | null> = ref(null)
 const remoteData: Ref<Array<string> | null> = ref(null)
 const isLoading = ref(false)
 const isLoading = ref(false)
 
 
-
 if (props.data) {
 if (props.data) {
-  items.value = props.multiple ? (data.value ?? []) : [data.value]
-
+  items.value = props.multiple ? data.value ?? [] : [data.value]
 } else if (props.remoteUri) {
 } else if (props.remoteUri) {
+  const ids: Array<any> = []
 
 
-  const ids:Array<any> = []
-
-  for(const uri of props.remoteUri){
+  for (const uri of props.remoteUri) {
     ids.push(UrlUtils.extractIdFromUri(uri as string))
     ids.push(UrlUtils.extractIdFromUri(uri as string))
   }
   }
 
 
-  const options: FetchOptions = { method: 'GET', query: {key: 'id', value: ids.join(',')} }
+  const options: FetchOptions = {
+    method: 'GET',
+    query: { key: 'id', value: ids.join(',') },
+  }
 
 
   useFetch(async () => {
   useFetch(async () => {
     isLoading.value = true
     isLoading.value = true
@@ -128,14 +127,14 @@ if (props.data) {
   })
   })
 }
 }
 
 
-const search = async (research:string) => {
+const search = async (research: string) => {
   isLoading.value = true
   isLoading.value = true
   const func: Function = props.searchFunction
   const func: Function = props.searchFunction
   items.value = items.value.concat(await func(research, props.field))
   items.value = items.value.concat(await func(research, props.field))
   isLoading.value = false
   isLoading.value = false
 }
 }
 
 
-const unwatch = watch(data,(d) => {
+const unwatch = watch(data, (d) => {
   items.value = props.multiple ? d : [d]
   items.value = props.multiple ? d : [d]
 })
 })
 
 

+ 37 - 28
components/Ui/Input/AutocompleteWithAp2i.vue

@@ -22,14 +22,13 @@ Liste déroulante avec autocompletion issue de Ap2i
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {computed} from "@vue/reactivity";
-import type {ComputedRef, Ref} from "@vue/reactivity";
-import type {PropType} from "@vue/runtime-core";
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import ApiResource from "~/models/ApiResource";
-import ApiModel from "~/models/ApiModel";
-import type {AnyJson, AssociativeArray} from "~/types/data";
+import { computed } from '@vue/reactivity'
+import type { ComputedRef, Ref } from '@vue/reactivity'
+import type { PropType } from '@vue/runtime-core'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import ApiResource from '~/models/ApiResource'
+import ApiModel from '~/models/ApiModel'
+import type { AnyJson, AssociativeArray } from '~/types/data'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -38,14 +37,14 @@ const props = defineProps({
   modelValue: {
   modelValue: {
     type: [String, Number, Object, Array],
     type: [String, Number, Object, Array],
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Classe de l'ApiModel (ex: Organization, Notification, ...) qui sert de source à la liste
    * Classe de l'ApiModel (ex: Organization, Notification, ...) qui sert de source à la liste
    */
    */
   model: {
   model: {
     type: Function as any as () => typeof ApiModel,
     type: Function as any as () => typeof ApiModel,
-    required: true
+    required: true,
   },
   },
   /**
   /**
    * Filtres à transmettre à la source de données
    * Filtres à transmettre à la source de données
@@ -53,7 +52,7 @@ const props = defineProps({
   query: {
   query: {
     type: Object as PropType<Ref<AssociativeArray>>,
     type: Object as PropType<Ref<AssociativeArray>>,
     required: false,
     required: false,
-    default: ref(null)
+    default: ref(null),
   },
   },
   /**
   /**
    * Fonction qui sera exécutée sur chaque item, et qui doit renvoyer un objet contenant les
    * Fonction qui sera exécutée sur chaque item, et qui doit renvoyer un objet contenant les
@@ -63,9 +62,11 @@ const props = defineProps({
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-item-value
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-item-value
    */
    */
   transformation: {
   transformation: {
-    type: Function as PropType<(item: ApiResource) => { id: number | string, title: string }>,
+    type: Function as PropType<
+      (item: ApiResource) => { id: number | string; title: string }
+    >,
     required: false,
     required: false,
-    default: (item: ApiResource) => item
+    default: (item: ApiResource) => item,
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -75,7 +76,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -84,7 +85,7 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Définit si le champ est en lecture seule
    * Définit si le champ est en lecture seule
@@ -92,7 +93,7 @@ const props = defineProps({
    */
    */
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Autorise la sélection multiple
    * Autorise la sélection multiple
@@ -100,7 +101,7 @@ const props = defineProps({
    */
    */
   multiple: {
   multiple: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Rends les résultats sous forme de puces
    * Rends les résultats sous forme de puces
@@ -108,30 +109,38 @@ const props = defineProps({
    */
    */
   chips: {
   chips: {
     type: Boolean,
     type: Boolean,
-    default: false
+    default: false,
   },
   },
   // TODO: c'est quoi?
   // TODO: c'est quoi?
   slotText: {
   slotText: {
     type: Array,
     type: Array,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
 })
 })
 
 
 const { fetchCollection } = useEntityFetch()
 const { fetchCollection } = useEntityFetch()
 
 
 const query: ComputedRef<AnyJson> = computed(() => {
 const query: ComputedRef<AnyJson> = computed(() => {
-  return { ...(props.query.value ?? {}), ...{ 'groups[]': 'access_people_ref' } }
+  return {
+    ...(props.query.value ?? {}),
+    ...{ 'groups[]': 'access_people_ref' },
+  }
 })
 })
 
 
-const { data: collection, pending } = await fetchCollection(props.model, null, query)
+const { data: collection, pending } = await fetchCollection(
+  props.model,
+  null,
+  query,
+)
 
 
-const items: ComputedRef<Array<{ id: number | string, title: string }>> = computed(() => {
-  if (!pending.value && collection.value && collection.value.items) {
-    console.log(collection)
+const items: ComputedRef<Array<{ id: number | string; title: string }>> =
+  computed(() => {
+    if (!pending.value && collection.value && collection.value.items) {
+      console.log(collection)
 
 
-    return collection.value.items.map(props.transformation)
-  }
-  return []
-})
+      return collection.value.items.map(props.transformation)
+    }
+    return []
+  })
 </script>
 </script>

+ 31 - 28
components/Ui/Input/AutocompleteWithEnum.vue

@@ -1,55 +1,61 @@
-
 <template>
 <template>
   <UiInputAutocomplete
   <UiInputAutocomplete
-      :model-value="modelValue"
-      :field="field"
-      :items="items"
-      :is-loading="pending"
-      :return-object="false"
-      item-title="label"
-      item-value="value"
-      :variant="variant"
-      @update:model-value="$emit('update:model-value', $event)"
+    :model-value="modelValue"
+    :field="field"
+    :items="items"
+    :is-loading="pending"
+    :return-object="false"
+    item-title="label"
+    item-value="value"
+    :variant="variant"
+    @update:model-value="$emit('update:model-value', $event)"
   />
   />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-
-import {useEnumFetch} from "~/composables/data/useEnumFetch";
-import ArrayUtils from "~/services/utils/arrayUtils";
-import type {ComputedRef} from "@vue/reactivity";
-import type {Enum} from "~/types/data";
-import type {PropType} from "@vue/runtime-core";
+import { useEnumFetch } from '~/composables/data/useEnumFetch'
+import ArrayUtils from '~/services/utils/arrayUtils'
+import type { ComputedRef } from '@vue/reactivity'
+import type { Enum } from '~/types/data'
+import type { PropType } from '@vue/runtime-core'
 
 
 const props = defineProps({
 const props = defineProps({
   modelValue: {
   modelValue: {
     type: String as PropType<string | null>,
     type: String as PropType<string | null>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   enumName: {
   enumName: {
     type: String,
     type: String,
-    required: true
+    required: true,
   },
   },
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    */
    */
   variant: {
   variant: {
-    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    type: String as PropType<
+      | 'filled'
+      | 'outlined'
+      | 'plain'
+      | 'underlined'
+      | 'solo'
+      | 'solo-inverted'
+      | 'solo-filled'
+      | undefined
+    >,
     required: false,
     required: false,
-    default: 'filled'
-  }
+    default: 'filled',
+  },
 })
 })
 
 
 const { fetch } = useEnumFetch()
 const { fetch } = useEnumFetch()
@@ -62,9 +68,6 @@ const items: ComputedRef<Array<Enum>> = computed(() => {
   }
   }
   return ArrayUtils.sortObjectsByProp(enumItems.value, 'label') as Array<Enum>
   return ArrayUtils.sortObjectsByProp(enumItems.value, 'label') as Array<Enum>
 })
 })
-
 </script>
 </script>
 
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 11 - 13
components/Ui/Input/Checkbox.vue

@@ -17,7 +17,7 @@ Case à cocher, à placer dans un composant `UiForm`
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -25,7 +25,7 @@ const props = defineProps({
    */
    */
   modelValue: {
   modelValue: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -35,7 +35,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -44,7 +44,7 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Définit si le champ est en lecture seule
    * Définit si le champ est en lecture seule
@@ -52,7 +52,7 @@ const props = defineProps({
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Règles de validation
    * Règles de validation
@@ -61,14 +61,14 @@ const props = defineProps({
   rules: {
   rules: {
     type: Array,
     type: Array,
     required: false,
     required: false,
-    default: () => []
+    default: () => [],
   },
   },
   /**
   /**
    * Le champ est-il actuellement en état d'erreur
    * Le champ est-il actuellement en état d'erreur
    */
    */
   error: {
   error: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Si le champ est en état d'erreur, quel est le message d'erreur?
    * Si le champ est en état d'erreur, quel est le message d'erreur?
@@ -76,11 +76,11 @@ const props = defineProps({
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
-const {fieldViolations, updateViolationState} = useFieldViolation(props.field)
+const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
 
 
 const fieldLabel: string = props.label ?? props.field
 const fieldLabel: string = props.label ?? props.field
 
 
@@ -90,8 +90,6 @@ const onUpdate = (event: boolean) => {
   updateViolationState(event)
   updateViolationState(event)
   emit('update:model-value', event)
   emit('update:model-value', event)
 }
 }
-
 </script>
 </script>
 
 
-<style scoped>
-</style>
+<style scoped></style>

+ 16 - 19
components/Ui/Input/Combobox.vue

@@ -5,10 +5,7 @@ Liste déroulante, à placer dans un composant `UiForm`
 -->
 -->
 
 
 <template>
 <template>
-  <v-container
-    class="px-0"
-    fluid
-  >
+  <v-container class="px-0" fluid>
     <v-combobox
     <v-combobox
       :model-value="modelValue"
       :model-value="modelValue"
       :value="modelValue"
       :value="modelValue"
@@ -16,14 +13,16 @@ Liste déroulante, à placer dans un composant `UiForm`
       :items="items"
       :items="items"
       :disabled="readonly"
       :disabled="readonly"
       :error="error || !!fieldViolations"
       :error="error || !!fieldViolations"
-      :error-messages="errorMessage || fieldViolations ? $t(fieldViolations) : ''"
+      :error-messages="
+        errorMessage || fieldViolations ? $t(fieldViolations) : ''
+      "
       @update:model-value="onUpdate($event)"
       @update:model-value="onUpdate($event)"
     />
     />
   </v-container>
   </v-container>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -31,7 +30,7 @@ const props = defineProps({
    */
    */
   modelValue: {
   modelValue: {
     type: [String, Number],
     type: [String, Number],
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -41,7 +40,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -50,21 +49,21 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Liste des éléments de la liste
    * Liste des éléments de la liste
    */
    */
   items: {
   items: {
     type: Array,
     type: Array,
-    required: true
+    required: true,
   },
   },
   /**
   /**
    * Définit si le champ est en lecture seule
    * Définit si le champ est en lecture seule
    */
    */
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Règles de validation
    * Règles de validation
@@ -73,14 +72,14 @@ const props = defineProps({
   rules: {
   rules: {
     type: Array,
     type: Array,
     required: false,
     required: false,
-    default: () => []
+    default: () => [],
   },
   },
   /**
   /**
    * Le champ est-il actuellement en état d'erreur
    * Le champ est-il actuellement en état d'erreur
    */
    */
   error: {
   error: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Si le champ est en état d'erreur, quel est le message d'erreur?
    * Si le champ est en état d'erreur, quel est le message d'erreur?
@@ -88,11 +87,11 @@ const props = defineProps({
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
-const {fieldViolations, updateViolationState} = useFieldViolation(props.field)
+const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
 
 
 const fieldLabel: string = props.label ?? props.field
 const fieldLabel: string = props.label ?? props.field
 
 
@@ -102,8 +101,6 @@ const onUpdate = (event: string) => {
   updateViolationState(event)
   updateViolationState(event)
   emit('update:model-value', event)
   emit('update:model-value', event)
 }
 }
-
 </script>
 </script>
 
 
-<style scoped>
-</style>
+<style scoped></style>

+ 20 - 22
components/Ui/Input/DatePicker.vue

@@ -8,11 +8,11 @@ Sélecteur de dates, à placer dans un composant `UiForm`
       <span>{{ $t(fieldLabel) }}</span>
       <span>{{ $t(fieldLabel) }}</span>
 
 
       <UiDatePicker
       <UiDatePicker
-          v-model="date"
-          :readonly="readonly"
-          :format="format"
-          :position="position"
-          @update:model-value="onUpdate($event)"
+        v-model="date"
+        :readonly="readonly"
+        :format="format"
+        :position="position"
+        @update:model-value="onUpdate($event)"
       />
       />
 
 
       <span v-if="error || !!fieldViolations" class="theme-danger">
       <span v-if="error || !!fieldViolations" class="theme-danger">
@@ -23,9 +23,9 @@ Sélecteur de dates, à placer dans un composant `UiForm`
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
-import {formatISO} from "date-fns";
-import type {PropType} from "@vue/runtime-core";
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
+import { formatISO } from 'date-fns'
+import type { PropType } from '@vue/runtime-core'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -34,7 +34,7 @@ const props = defineProps({
   modelValue: {
   modelValue: {
     type: String as PropType<string | null>,
     type: String as PropType<string | null>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -44,7 +44,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -53,14 +53,14 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Définit si le champ est en lecture seule
    * Définit si le champ est en lecture seule
    */
    */
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Format d'affichage des dates
    * Format d'affichage des dates
@@ -69,7 +69,7 @@ const props = defineProps({
   format: {
   format: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Règles de validation
    * Règles de validation
@@ -78,14 +78,14 @@ const props = defineProps({
   rules: {
   rules: {
     type: Array,
     type: Array,
     required: false,
     required: false,
-    default: () => []
+    default: () => [],
   },
   },
   /**
   /**
    * Le champ est-il actuellement en état d'erreur
    * Le champ est-il actuellement en état d'erreur
    */
    */
   error: {
   error: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Si le champ est en état d'erreur, quel est le message d'erreur?
    * Si le champ est en état d'erreur, quel est le message d'erreur?
@@ -93,7 +93,7 @@ const props = defineProps({
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * @see https://vue3datepicker.com/props/positioning/#position
    * @see https://vue3datepicker.com/props/positioning/#position
@@ -101,13 +101,13 @@ const props = defineProps({
   position: {
   position: {
     type: String as PropType<'left' | 'center' | 'right'>,
     type: String as PropType<'left' | 'center' | 'right'>,
     required: false,
     required: false,
-    default: 'center'
-  }
+    default: 'center',
+  },
 })
 })
 
 
 const input = ref(null)
 const input = ref(null)
 
 
-const {fieldViolations, updateViolationState} = useFieldViolation(props.field)
+const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
 
 
 const fieldLabel = props.label ?? props.field
 const fieldLabel = props.label ?? props.field
 
 
@@ -121,6 +121,4 @@ const onUpdate = (event: string) => {
 }
 }
 </script>
 </script>
 
 
-<style scoped>
-
-</style>
+<style scoped></style>

+ 14 - 18
components/Ui/Input/Email.vue

@@ -15,64 +15,60 @@ Champs de saisie de type Text dédié à la saisie d'emails
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {useNuxtApp} from "#app";
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
-import {useValidationUtils} from "~/composables/utils/useValidationUtils";
+import { useNuxtApp } from '#app'
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
+import { useValidationUtils } from '~/composables/utils/useValidationUtils'
 
 
 const props = defineProps({
 const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   data: {
   data: {
     type: [String, Number],
     type: [String, Number],
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   required: {
   required: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   error: {
   error: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
 const { emit, i18n } = useNuxtApp()
 const { emit, i18n } = useNuxtApp()
 
 
 const fieldLabel = props.label ?? props.field
 const fieldLabel = props.label ?? props.field
 
 
-const {violation, onChange} = useFieldViolation(props.field, emit)
+const { violation, onChange } = useFieldViolation(props.field, emit)
 
 
 const validationUtils = useValidationUtils()
 const validationUtils = useValidationUtils()
 
 
 const rules = [
 const rules = [
-  (email: string) => validationUtils.validEmail(email) || i18n.t('email_error')
+  (email: string) => validationUtils.validEmail(email) || i18n.t('email_error'),
 ]
 ]
 
 
 if (props.required) {
 if (props.required) {
-  rules.push(
-    (email: string) => !!email || i18n.t('required')
-  )
+  rules.push((email: string) => !!email || i18n.t('required'))
 }
 }
-
 </script>
 </script>

+ 20 - 21
components/Ui/Input/Enum.vue

@@ -6,11 +6,7 @@ Liste déroulante dédiée à l'affichage d'objets Enum
 
 
 <template>
 <template>
   <main>
   <main>
-    <v-skeleton-loader
-      v-if="pending"
-      type="list-item"
-      loading
-    />
+    <v-skeleton-loader v-if="pending" type="list-item" loading />
 
 
     <v-select
     <v-select
       v-else
       v-else
@@ -23,15 +19,20 @@ Liste déroulante dédiée à l'affichage d'objets Enum
       :rules="rules"
       :rules="rules"
       :disabled="readonly"
       :disabled="readonly"
       :error="error || !!fieldViolations"
       :error="error || !!fieldViolations"
-      :error-messages="errorMessage || (fieldViolations ? $t(fieldViolations) : '')"
-      @update:modelValue="updateViolationState($event); $emit('update:modelValue', $event)"
+      :error-messages="
+        errorMessage || (fieldViolations ? $t(fieldViolations) : '')
+      "
+      @update:modelValue="
+        updateViolationState($event)
+        $emit('update:modelValue', $event)
+      "
     />
     />
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
-import {useEnumFetch} from "~/composables/data/useEnumFetch";
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
+import { useEnumFetch } from '~/composables/data/useEnumFetch'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -40,14 +41,14 @@ const props = defineProps({
   modelValue: {
   modelValue: {
     String,
     String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Nom de l'Enum utilisée pour peupler la liste
    * Nom de l'Enum utilisée pour peupler la liste
    */
    */
   enum: {
   enum: {
     type: String,
     type: String,
-    required: true
+    required: true,
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -57,7 +58,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -66,14 +67,14 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Définit si le champ est en lecture seule
    * Définit si le champ est en lecture seule
    */
    */
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Règles de validation
    * Règles de validation
@@ -82,14 +83,14 @@ const props = defineProps({
   rules: {
   rules: {
     type: Array,
     type: Array,
     required: false,
     required: false,
-    default: () => []
+    default: () => [],
   },
   },
   /**
   /**
    * Le champ est-il actuellement en état d'erreur
    * Le champ est-il actuellement en état d'erreur
    */
    */
   error: {
   error: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Si le champ est en état d'erreur, quel est le message d'erreur ?
    * Si le champ est en état d'erreur, quel est le message d'erreur ?
@@ -97,8 +98,8 @@ const props = defineProps({
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
 if (typeof props.enum === 'undefined') {
 if (typeof props.enum === 'undefined') {
@@ -117,8 +118,6 @@ const onModelUpdate = (event: any) => {
   emit('change', event)
   emit('change', event)
   emit('update:modelValue', event)
   emit('update:modelValue', event)
 }
 }
-
 </script>
 </script>
 
 
-<style scoped>
-</style>
+<style scoped></style>

+ 147 - 127
components/Ui/Input/Image.vue

@@ -4,16 +4,16 @@ Assistant de création d'image
 @see https://norserium.github.io/vue-advanced-cropper/
 @see https://norserium.github.io/vue-advanced-cropper/
 -->
 -->
 <template>
 <template>
-  <div class="input-image" >
+  <div class="input-image">
     <UiImage
     <UiImage
-        ref="uiImage"
-        :image-id="modelValue"
-        :default-image="defaultImage"
-        :width="width"
-        :height="height"
-        class="image"
-        overlay-icon="fas fa-upload"
-        @overlay-clicked="openModal()"
+      ref="uiImage"
+      :image-id="modelValue"
+      :default-image="defaultImage"
+      :width="width"
+      :height="height"
+      class="image"
+      overlay-icon="fas fa-upload"
+      @overlay-clicked="openModal()"
     />
     />
 
 
     <LazyLayoutDialog :show="showModal">
     <LazyLayoutDialog :show="showModal">
@@ -27,13 +27,11 @@ Assistant de création d'image
             align="center"
             align="center"
             justify="center"
             justify="center"
           >
           >
-            <v-progress-circular
-              :indeterminate="true"
-              color="neutral">
+            <v-progress-circular :indeterminate="true" color="neutral">
             </v-progress-circular>
             </v-progress-circular>
           </v-row>
           </v-row>
 
 
-          <div v-else >
+          <div v-else>
             <div class="upload__cropper-wrapper">
             <div class="upload__cropper-wrapper">
               <Cropper
               <Cropper
                 ref="cropper"
                 ref="cropper"
@@ -46,10 +44,10 @@ Assistant de création d'image
               />
               />
 
 
               <div
               <div
-                  v-if="currentImage.src"
-                  class="upload__reset-button"
-                  title="Reset Image"
-                  @click="reset()"
+                v-if="currentImage.src"
+                class="upload__reset-button"
+                title="Reset Image"
+                @click="reset()"
               >
               >
                 <v-icon>fas fa-trash</v-icon>
                 <v-icon>fas fa-trash</v-icon>
               </div>
               </div>
@@ -57,21 +55,29 @@ Assistant de création d'image
 
 
             <div class="upload__buttons-wrapper">
             <div class="upload__buttons-wrapper">
               <button class="upload__button" @click="fileInput?.click()">
               <button class="upload__button" @click="fileInput?.click()">
-                <input ref="fileInput" type="file" accept="image/*" @change="uploadImage($event)" />
-                {{$t('upload_image')}}
+                <input
+                  ref="fileInput"
+                  type="file"
+                  accept="image/*"
+                  @change="uploadImage($event)"
+                />
+                {{ $t('upload_image') }}
               </button>
               </button>
             </div>
             </div>
 
 
             <span class="max-size-label">{{ $t('max_size_4_mb') }}</span>
             <span class="max-size-label">{{ $t('max_size_4_mb') }}</span>
           </div>
           </div>
-
         </div>
         </div>
       </template>
       </template>
       <template #dialogBtn>
       <template #dialogBtn>
         <v-btn class="mr-4 submitBtn theme-neutral-strong" @click="cancel">
         <v-btn class="mr-4 submitBtn theme-neutral-strong" @click="cancel">
           {{ $t('cancel') }}
           {{ $t('cancel') }}
         </v-btn>
         </v-btn>
-        <v-btn class="submitBtn theme-primary" @click="save" :disabled="pending">
+        <v-btn
+          class="submitBtn theme-primary"
+          @click="save"
+          :disabled="pending"
+        >
           {{ $t('save') }}
           {{ $t('save') }}
         </v-btn>
         </v-btn>
       </template>
       </template>
@@ -80,16 +86,16 @@ Assistant de création d'image
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { Cropper } from 'vue-advanced-cropper';
-import 'vue-advanced-cropper/dist/style.css';
-import {type Ref, ref} from "@vue/reactivity";
+import { Cropper } from 'vue-advanced-cropper'
+import 'vue-advanced-cropper/dist/style.css'
+import { type Ref, ref } from '@vue/reactivity'
 import File from '~/models/Core/File'
 import File from '~/models/Core/File'
-import type {PropType} from "@vue/runtime-core";
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import {useImageManager} from "~/composables/data/useImageManager";
-import {FILE_VISIBILITY, TYPE_ALERT} from "~/types/enum/enums";
-import {usePageStore} from "~/stores/page";
-import ImageUtils from "~/services/utils/imageUtils";
+import type { PropType } from '@vue/runtime-core'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import { useImageManager } from '~/composables/data/useImageManager'
+import { FILE_VISIBILITY, TYPE_ALERT } from '~/types/enum/enums'
+import { usePageStore } from '~/stores/page'
+import ImageUtils from '~/services/utils/imageUtils'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -98,7 +104,7 @@ const props = defineProps({
   modelValue: {
   modelValue: {
     type: Number as PropType<number | null>,
     type: Number as PropType<number | null>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -107,36 +113,36 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Image par défaut en cas d'absence d'une image uploadée
    * Image par défaut en cas d'absence d'une image uploadée
    */
    */
   defaultImage: {
   defaultImage: {
     type: String,
     type: String,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Hauteur de l'image à l'écran (en px)
    * Hauteur de l'image à l'écran (en px)
    */
    */
   height: {
   height: {
     type: Number,
     type: Number,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * Largeur de l'image à l'écran (en px)
    * Largeur de l'image à l'écran (en px)
    */
    */
   width: {
   width: {
     type: Number,
     type: Number,
-    required: false
+    required: false,
   },
   },
   /**
   /**
    * TODO: completer
    * TODO: completer
    */
    */
   ownerId: {
   ownerId: {
     type: Number,
     type: Number,
-    required: false
-  }
+    required: false,
+  },
 })
 })
 
 
 const { em } = useEntityManager()
 const { em } = useEntityManager()
@@ -170,13 +176,16 @@ const file: Ref<File | null> = ref(null)
 /**
 /**
  * Données d'une nouvelle image uploadée par l'utilisateur
  * Données d'une nouvelle image uploadée par l'utilisateur
  */
  */
-const currentImage: Ref<
-    {id: string | number | null, src: string | null, content: string | null, name: string | null}
-> = ref({
+const currentImage: Ref<{
+  id: string | number | null
+  src: string | null
+  content: string | null
+  name: string | null
+}> = ref({
   id: null,
   id: null,
   src: null,
   src: null,
   content: null,
   content: null,
-  name: null
+  name: null,
 })
 })
 
 
 /**
 /**
@@ -187,19 +196,24 @@ const MAX_FILE_SIZE = 4 * 1024 * 1024
 /**
 /**
  * Coordonnées du cropper
  * Coordonnées du cropper
  */
  */
-const cropperConfig: Ref<{ left?: number, top?: number, height?: number, width?: number }> = ref({})
+const cropperConfig: Ref<{
+  left?: number
+  top?: number
+  height?: number
+  width?: number
+}> = ref({})
 
 
 /**
 /**
  * @see https://advanced-cropper.github.io/vue-advanced-cropper/components/cropper.html#defaultposition
  * @see https://advanced-cropper.github.io/vue-advanced-cropper/components/cropper.html#defaultposition
  */
  */
 const defaultPosition = () => {
 const defaultPosition = () => {
-  return { left : cropperConfig.value.left, top : cropperConfig.value.top }
+  return { left: cropperConfig.value.left, top: cropperConfig.value.top }
 }
 }
 
 
 /**
 /**
  * @see https://advanced-cropper.github.io/vue-advanced-cropper/components/cropper.html#defaultsize
  * @see https://advanced-cropper.github.io/vue-advanced-cropper/components/cropper.html#defaultsize
  */
  */
-const defaultSize = (params: any): { width: number, height: number } | null => {
+const defaultSize = (params: any): { width: number; height: number } | null => {
   if (!params) {
   if (!params) {
     return null
     return null
   }
   }
@@ -207,7 +221,7 @@ const defaultSize = (params: any): { width: number, height: number } | null => {
 
 
   return {
   return {
     width: cropperConfig.value.width ?? (visibleArea || imageSize).width,
     width: cropperConfig.value.width ?? (visibleArea || imageSize).width,
-    height: cropperConfig.value.height ?? (visibleArea || imageSize).height
+    height: cropperConfig.value.height ?? (visibleArea || imageSize).height,
   }
   }
 }
 }
 
 
@@ -217,7 +231,7 @@ const defaultSize = (params: any): { width: number, height: number } | null => {
  * @param fileId
  * @param fileId
  */
  */
 const loadImage = async (fileId: number) => {
 const loadImage = async (fileId: number) => {
-  file.value = await em.fetch(File, fileId) as File
+  file.value = (await em.fetch(File, fileId)) as File
 
 
   if (file.value.config) {
   if (file.value.config) {
     const fileConfig = JSON.parse(file.value.config)
     const fileConfig = JSON.parse(file.value.config)
@@ -229,7 +243,7 @@ const loadImage = async (fileId: number) => {
 
 
   currentImage.value.name = file.value.name
   currentImage.value.name = file.value.name
   currentImage.value.id = file.value.id
   currentImage.value.id = file.value.id
-  currentImage.value.src = await imageManager.get(fileId) as string
+  currentImage.value.src = (await imageManager.get(fileId)) as string
 }
 }
 
 
 /**
 /**
@@ -242,7 +256,6 @@ const openModal = async () => {
   if (props.modelValue !== null) {
   if (props.modelValue !== null) {
     // Un objet File existe déjà: on le récupère
     // Un objet File existe déjà: on le récupère
     await loadImage(props.modelValue)
     await loadImage(props.modelValue)
-
   } else {
   } else {
     // Nouveau File
     // Nouveau File
     file.value = em.newInstance(File) as File
     file.value = em.newInstance(File) as File
@@ -260,11 +273,17 @@ const reset = () => {
   }
   }
 
 
   currentImage.value = {
   currentImage.value = {
-    src: null, content: null, name: null, id: null
+    src: null,
+    content: null,
+    name: null,
+    id: null,
   }
   }
 
 
   cropperConfig.value = {
   cropperConfig.value = {
-    left: undefined, height: undefined, top: undefined, width: undefined
+    left: undefined,
+    height: undefined,
+    top: undefined,
+    width: undefined,
   }
   }
 }
 }
 
 
@@ -282,7 +301,10 @@ const uploadImage = async (event: any) => {
   const uploadedFile = files[0]
   const uploadedFile = files[0]
 
 
   if (uploadedFile.size > MAX_FILE_SIZE) {
   if (uploadedFile.size > MAX_FILE_SIZE) {
-    pageStore.alerts.push({type: TYPE_ALERT.ALERT, messages: ['file_too_large'] })
+    pageStore.alerts.push({
+      type: TYPE_ALERT.ALERT,
+      messages: ['file_too_large'],
+    })
     return
     return
   }
   }
 
 
@@ -303,8 +325,8 @@ const uploadImage = async (event: any) => {
  * Lorsque le cropper change de position / taille, on met à jour les coordonnées
  * Lorsque le cropper change de position / taille, on met à jour les coordonnées
  * @param newCoordinates
  * @param newCoordinates
  */
  */
-const onCropperChange = ({ coordinates: newCoordinates } : any) => {
-  cropperConfig.value = newCoordinates;
+const onCropperChange = ({ coordinates: newCoordinates }: any) => {
+  cropperConfig.value = newCoordinates
 }
 }
 
 
 /**
 /**
@@ -333,15 +355,15 @@ const saveNewImage = async (): Promise<number> => {
     x: cropperConfig.value.left,
     x: cropperConfig.value.left,
     y: cropperConfig.value.top,
     y: cropperConfig.value.top,
     height: cropperConfig.value.height,
     height: cropperConfig.value.height,
-    width: cropperConfig.value.width
+    width: cropperConfig.value.width,
   })
   })
 
 
-  const response = await imageManager.upload(
-      currentImage.value.name,
-      currentImage.value.content,
-      FILE_VISIBILITY.EVERYBODY,
-      config
-  ) as any
+  const response = (await imageManager.upload(
+    currentImage.value.name,
+    currentImage.value.content,
+    FILE_VISIBILITY.EVERYBODY,
+    config,
+  )) as any
 
 
   return response.fileId
   return response.fileId
 }
 }
@@ -358,7 +380,7 @@ const saveExistingImage = async () => {
     x: cropperConfig.value.left,
     x: cropperConfig.value.left,
     y: cropperConfig.value.top,
     y: cropperConfig.value.top,
     height: cropperConfig.value.height,
     height: cropperConfig.value.height,
-    width: cropperConfig.value.width
+    width: cropperConfig.value.width,
   })
   })
 
 
   await em.persist(File, file.value)
   await em.persist(File, file.value)
@@ -375,12 +397,10 @@ const save = async () => {
     // Une nouvelle image a été uploadée
     // Une nouvelle image a été uploadée
     const fileId = await saveNewImage()
     const fileId = await saveNewImage()
     emit('update:modelValue', fileId)
     emit('update:modelValue', fileId)
-
   } else if (currentImage.value.id) {
   } else if (currentImage.value.id) {
     // L'image existante a été modifiée
     // L'image existante a été modifiée
     await saveExistingImage()
     await saveExistingImage()
     uiImage.value.refresh()
     uiImage.value.refresh()
-
   } else {
   } else {
     // On a reset l'image
     // On a reset l'image
     emit('update:modelValue', null)
     emit('update:modelValue', null)
@@ -401,74 +421,74 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  :deep(.vue-advanced-cropper__stretcher) {
-    height: auto !important;
-    width: auto !important;
-  }
+:deep(.vue-advanced-cropper__stretcher) {
+  height: auto !important;
+  width: auto !important;
+}
 
 
-  .loading{
-    height: 300px;
-  }
+.loading {
+  height: 300px;
+}
 
 
-  .upload {
-    user-select: none;
-    padding: 20px;
-    display: block;
-    &__cropper {
-       border: solid 1px rgb(var(--v-theme-on-neutral-strong));;
-       min-height: 300px;
-       max-height: 300px;
-     }
-    &__cropper-wrapper {
-       position: relative;
-     }
-    &__reset-button {
-      position: absolute;
-      right: 20px;
-      bottom: 20px;
-      cursor: pointer;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      height: 42px;
-      width: 42px;
-      background: rgb(var(--v-theme-neutral));
-      transition: background 0.5s;
-      &:hover {
-        background: rgb(var(--v-theme-primary-alt));
-      }
+.upload {
+  user-select: none;
+  padding: 20px;
+  display: block;
+  &__cropper {
+    border: solid 1px rgb(var(--v-theme-on-neutral-strong));
+    min-height: 300px;
+    max-height: 300px;
+  }
+  &__cropper-wrapper {
+    position: relative;
+  }
+  &__reset-button {
+    position: absolute;
+    right: 20px;
+    bottom: 20px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 42px;
+    width: 42px;
+    background: rgb(var(--v-theme-neutral));
+    transition: background 0.5s;
+    &:hover {
+      background: rgb(var(--v-theme-primary-alt));
     }
     }
-    &__buttons-wrapper {
-       display: flex;
-       justify-content: center;
-       margin-top: 17px;
-     }
-    &__button {
-       border: none;
-       outline: solid transparent;
-       color: rgb(var(--v-theme-on-neutral));
-       font-size: 16px;
-       padding: 10px 20px;
-       background: rgb(var(--v-theme-neutral));
-       cursor: pointer;
-       transition: background 0.5s;
-       margin: 0 16px;
-      &:hover,
-      &:focus {
-         background: rgb(var(--v-theme-primary-alt));
-       }
-      input {
-        display: none;
-      }
+  }
+  &__buttons-wrapper {
+    display: flex;
+    justify-content: center;
+    margin-top: 17px;
+  }
+  &__button {
+    border: none;
+    outline: solid transparent;
+    color: rgb(var(--v-theme-on-neutral));
+    font-size: 16px;
+    padding: 10px 20px;
+    background: rgb(var(--v-theme-neutral));
+    cursor: pointer;
+    transition: background 0.5s;
+    margin: 0 16px;
+    &:hover,
+    &:focus {
+      background: rgb(var(--v-theme-primary-alt));
+    }
+    input {
+      display: none;
     }
     }
   }
   }
+}
 
 
-  .max-size-label {
-    display: block;
-    width: 100%;
-    text-align: center;
-    font-size: 13px;
-    color: rgb(var(--v-theme-on-neutral-soft));
-    margin-top: 6px;
-  }
+.max-size-label {
+  display: block;
+  width: 100%;
+  text-align: center;
+  font-size: 13px;
+  color: rgb(var(--v-theme-on-neutral-soft));
+  margin-top: 6px;
+}
 </style>
 </style>

+ 34 - 21
components/Ui/Input/Number.vue

@@ -4,26 +4,25 @@ An input for numeric values
 
 
 <template>
 <template>
   <v-text-field
   <v-text-field
-      ref="input"
-      :modelValue.number="modelValue"
-      :label="(label || field) ? $t(label ?? field) : undefined"
-      hide-details
-      :density="density"
-      type="number"
-      :variant="variant"
-      @update:modelValue="modelValue = keepInRange(cast($event)); emitUpdate()"
+    ref="input"
+    :modelValue.number="modelValue"
+    :label="label || field ? $t(label ?? field) : undefined"
+    hide-details
+    :density="density"
+    type="number"
+    :variant="variant"
+    @update:modelValue="onModelUpdate($event)"
   />
   />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
+import type { PropType } from 'vue'
 
 
-import type {PropType} from "@vue/runtime-core";
-
-type Density = null | 'default' | 'comfortable' | 'compact';
+type Density = null | 'default' | 'comfortable' | 'compact'
 
 
 const props = defineProps({
 const props = defineProps({
   modelValue: {
   modelValue: {
-    type: Number
+    type: Number,
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -33,7 +32,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -42,36 +41,45 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   default: {
   default: {
     type: Number,
     type: Number,
     required: false,
     required: false,
-    default: 0
+    default: 0,
   },
   },
   min: {
   min: {
     type: Number,
     type: Number,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   max: {
   max: {
     type: Number,
     type: Number,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   density: {
   density: {
     type: String as PropType<Density>,
     type: String as PropType<Density>,
     required: false,
     required: false,
-    default: 'default'
+    default: 'default',
   },
   },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    */
    */
   variant: {
   variant: {
-    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    type: String as PropType<
+      | 'filled'
+      | 'outlined'
+      | 'plain'
+      | 'underlined'
+      | 'solo'
+      | 'solo-inverted'
+      | 'solo-filled'
+      | undefined
+    >,
     required: false,
     required: false,
-    default: 'filled'
-  }
+    default: 'filled',
+  },
 })
 })
 
 
 /**
 /**
@@ -109,6 +117,11 @@ const keepInRange = (val: number) => {
   return val
   return val
 }
 }
 
 
+const onModelUpdate = (event: string) => {
+  // eslint-disable-next-line vue/no-mutating-props
+  props.modelValue = keepInRange(cast(event))
+  emitUpdate()
+}
 
 
 const emit = defineEmits(['update:modelValue'])
 const emit = defineEmits(['update:modelValue'])
 
 

+ 28 - 24
components/Ui/Input/Phone.vue

@@ -26,40 +26,39 @@ Champs de saisie d'un numéro de téléphone
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {useNuxtApp} from "#app";
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
-import type {Ref} from "@vue/reactivity";
+import { useNuxtApp } from '#app'
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
+import type { Ref } from '@vue/reactivity'
 
 
 const props = defineProps({
 const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: ''
+    default: '',
   },
   },
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   data: {
   data: {
     type: [String, Number],
     type: [String, Number],
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   error: {
   error: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
 const { emit, i18n } = useNuxtApp()
 const { emit, i18n } = useNuxtApp()
@@ -71,7 +70,14 @@ const internationalNumber: Ref<string | number> = ref('')
 const isValid: Ref<boolean> = ref(false)
 const isValid: Ref<boolean> = ref(false)
 const onInit: Ref<boolean> = ref(true)
 const onInit: Ref<boolean> = ref(true)
 
 
-const onInput = (_: any, { number, valid, countryChoice }: { number: any, valid: boolean, countryChoice: any }) => {
+const onInput = (
+  _: any,
+  {
+    number,
+    valid,
+    countryChoice,
+  }: { number: any; valid: boolean; countryChoice: any },
+) => {
   isValid.value = valid
   isValid.value = valid
   nationalNumber.value = number.national
   nationalNumber.value = number.national
   internationalNumber.value = number.international
   internationalNumber.value = number.international
@@ -84,24 +90,22 @@ const onChangeValue = () => {
   }
   }
 }
 }
 
 
-const myPhone = computed(
-  {
-    get:()=>{
-      return onInit.value ? props.data : nationalNumber.value
-    },
-    set:(value)=>{
-      return props.data
-    }
-  }
-)
+const myPhone = computed({
+  get: () => {
+    return onInit.value ? props.data : nationalNumber.value
+  },
+  set: (value) => {
+    return props.data
+  },
+})
 
 
 const rules = [
 const rules = [
-  (phone: string) => (!phone || isValid.value) || i18n.t('phone_error')
+  (phone: string) => !phone || isValid.value || i18n.t('phone_error'),
 ]
 ]
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">
-input:read-only{
+input:read-only {
   color: rgb(var(--v-theme-on-neutral));
   color: rgb(var(--v-theme-on-neutral));
 }
 }
 </style>
 </style>

+ 33 - 23
components/Ui/Input/Text.vue

@@ -8,12 +8,14 @@ Champs de saisie de texte, à placer dans un composant `UiForm`
   <v-text-field
   <v-text-field
     ref="input"
     ref="input"
     :model-value="modelValue"
     :model-value="modelValue"
-    :label="(label || field) ? $t(label ?? field) : undefined"
+    :label="label || field ? $t(label ?? field) : undefined"
     :rules="rules"
     :rules="rules"
     :disabled="readonly"
     :disabled="readonly"
-    :type="(type === 'password' && show) ? 'text' : type"
+    :type="type === 'password' && show ? 'text' : type"
     :error="error || !!fieldViolations"
     :error="error || !!fieldViolations"
-    :error-messages="errorMessage || (fieldViolations ? $t(fieldViolations) : '')"
+    :error-messages="
+      errorMessage || (fieldViolations ? $t(fieldViolations) : '')
+    "
     :append-icon="type === 'password' ? (show ? 'mdi-eye' : 'mdi-eye-off') : ''"
     :append-icon="type === 'password' ? (show ? 'mdi-eye' : 'mdi-eye-off') : ''"
     :variant="variant"
     :variant="variant"
     @click:append="show = !show"
     @click:append="show = !show"
@@ -21,14 +23,13 @@ Champs de saisie de texte, à placer dans un composant `UiForm`
     @change="onChange($event)"
     @change="onChange($event)"
   />
   />
 
 
-
-<!--  v-cleave="mask"-->
+  <!--  v-cleave="mask"-->
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {type Ref, ref} from "@vue/reactivity";
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
-import type {PropType} from "@vue/runtime-core";
+import { type Ref, ref } from '@vue/reactivity'
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
+import type { PropType } from '@vue/runtime-core'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -37,7 +38,7 @@ const props = defineProps({
   modelValue: {
   modelValue: {
     type: [String, Number] as PropType<string | number | null>,
     type: [String, Number] as PropType<string | number | null>,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
@@ -47,7 +48,7 @@ const props = defineProps({
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Label du champ
    * Label du champ
@@ -56,7 +57,7 @@ const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Type d'input HTML
    * Type d'input HTML
@@ -65,7 +66,7 @@ const props = defineProps({
   type: {
   type: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Définit si le champ est en lecture seule
    * Définit si le champ est en lecture seule
@@ -73,7 +74,7 @@ const props = defineProps({
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Règles de validation
    * Règles de validation
@@ -82,7 +83,7 @@ const props = defineProps({
   rules: {
   rules: {
     type: Array as PropType<any[]>,
     type: Array as PropType<any[]>,
     required: false,
     required: false,
-    default: () => []
+    default: () => [],
   },
   },
   /**
   /**
    * Le champ est-il actuellement en état d'erreur
    * Le champ est-il actuellement en état d'erreur
@@ -90,7 +91,7 @@ const props = defineProps({
   error: {
   error: {
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * Si le champ est en état d'erreur, quel est le message d'erreur?
    * Si le champ est en état d'erreur, quel est le message d'erreur?
@@ -98,7 +99,7 @@ const props = defineProps({
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   /**
   /**
    * Masque de saisie
    * Masque de saisie
@@ -107,16 +108,25 @@ const props = defineProps({
   mask: {
   mask: {
     type: [Array, Boolean],
     type: [Array, Boolean],
     required: false,
     required: false,
-    default: false
+    default: false,
   },
   },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    */
    */
   variant: {
   variant: {
-    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    type: String as PropType<
+      | 'filled'
+      | 'outlined'
+      | 'plain'
+      | 'underlined'
+      | 'solo'
+      | 'solo-inverted'
+      | 'solo-filled'
+      | undefined
+    >,
     required: false,
     required: false,
-    default: 'filled'
-  }
+    default: 'filled',
+  },
 })
 })
 
 
 const input = ref(null)
 const input = ref(null)
@@ -138,7 +148,7 @@ const onChange = (event: Event | undefined) => {
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  input:read-only{
-    color: rgb(var(--v-theme-neutral));
-  }
+input:read-only {
+  color: rgb(var(--v-theme-neutral));
+}
 </style>
 </style>

+ 23 - 25
components/Ui/Input/TextArea.vue

@@ -6,68 +6,66 @@ Champs de saisie de bloc texte
 
 
 <template>
 <template>
   <v-textarea
   <v-textarea
-      outlined
-      :value="data"
-      :label="$t(fieldLabel)"
-      :rules="rules"
-      :disabled="readonly"
-      :error="error || !!violation"
-      :error-messages="errorMessage || violation ? $t(violation) : ''"
-      @change="onChange($event)"
-    />
+    outlined
+    :value="data"
+    :label="$t(fieldLabel)"
+    :rules="rules"
+    :disabled="readonly"
+    :error="error || !!violation"
+    :error-messages="errorMessage || violation ? $t(violation) : ''"
+    @change="onChange($event)"
+  />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
-import {useNuxtApp} from "#app";
-import {useFieldViolation} from "~/composables/form/useFieldViolation";
+import { useNuxtApp } from '#app'
+import { useFieldViolation } from '~/composables/form/useFieldViolation'
 
 
 const props = defineProps({
 const props = defineProps({
   label: {
   label: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   field: {
   field: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   data: {
   data: {
     type: [String, Number],
     type: [String, Number],
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   readonly: {
   readonly: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   rules: {
   rules: {
     type: Array,
     type: Array,
     required: false,
     required: false,
-    default: () => []
+    default: () => [],
   },
   },
   error: {
   error: {
     type: Boolean,
     type: Boolean,
-    required: false
+    required: false,
   },
   },
   errorMessage: {
   errorMessage: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
 const { emit } = useNuxtApp()
 const { emit } = useNuxtApp()
 
 
 const fieldLabel = props.label ?? props.field
 const fieldLabel = props.label ?? props.field
 
 
-const {violation, onChange} = useFieldViolation(props.field, emit)
-
+const { violation, onChange } = useFieldViolation(props.field, emit)
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">
-  input:read-only{
-    color: rgb(var(--v-theme-on-neutral));
-  }
+input:read-only {
+  color: rgb(var(--v-theme-on-neutral));
+}
 </style>
 </style>

+ 14 - 17
components/Ui/ItemFromUri.vue

@@ -3,12 +3,9 @@ Espace permettant de récupérer un item via une uri et de gérer son affichage
 -->
 -->
 <template>
 <template>
   <main>
   <main>
-    <v-skeleton-loader
-      v-if="pending"
-      :type="loaderType"
-    />
+    <v-skeleton-loader v-if="pending" :type="loaderType" />
     <div v-else>
     <div v-else>
-      <slot name="item.text" v-bind="{item}" />
+      <slot name="item.text" v-bind="{ item }" />
     </div>
     </div>
     <slot />
     <slot />
   </main>
   </main>
@@ -17,32 +14,32 @@ Espace permettant de récupérer un item via une uri et de gérer son affichage
 <script setup lang="ts">
 <script setup lang="ts">
 // TODO: renommer en EntityFromUri? voir si ce component est encore nécessaire, ou si ça ne peut pas être une méthode de l'entity manager
 // TODO: renommer en EntityFromUri? voir si ce component est encore nécessaire, ou si ça ne peut pas être une méthode de l'entity manager
 
 
-import {Query} from "pinia-orm";
-import UrlUtils from "~/services/utils/urlUtils";
-import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {computed} from "@vue/reactivity";
-import type {ComputedRef} from "@vue/reactivity";
-import ApiResource from "~/models/ApiResource";
+import { Query } from 'pinia-orm'
+import UrlUtils from '~/services/utils/urlUtils'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import { computed } from '@vue/reactivity'
+import type { ComputedRef } from '@vue/reactivity'
+import ApiResource from '~/models/ApiResource'
 
 
 const props = defineProps({
 const props = defineProps({
   uri: {
   uri: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: null
+    default: null,
   },
   },
   model: {
   model: {
     type: Object,
     type: Object,
-    required: true
+    required: true,
   },
   },
   query: {
   query: {
     type: Object as () => Query,
     type: Object as () => Query,
-    required: true
+    required: true,
   },
   },
   loaderType: {
   loaderType: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: 'text'
-  }
+    default: 'text',
+  },
 })
 })
 
 
 const id = UrlUtils.extractIdFromUri(props.uri)
 const id = UrlUtils.extractIdFromUri(props.uri)
@@ -54,7 +51,7 @@ const { fetch } = useEntityFetch()
 
 
 const { data, pending } = fetch(props.model, id)
 const { data, pending } = fetch(props.model, id)
 
 
-const item: ComputedRef<ApiResource|null> = computed(() => {
+const item: ComputedRef<ApiResource | null> = computed(() => {
   return data.value
   return data.value
 })
 })
 </script>
 </script>

+ 4 - 14
components/Ui/LoadingPanel.vue

@@ -1,19 +1,9 @@
 <template>
 <template>
-  <v-row
-      class="fill-height ma-0"
-      align="center"
-      justify="center"
-  >
-    <v-progress-circular
-        :indeterminate="true"
-        color="neutral"
-    />
+  <v-row class="fill-height ma-0" align="center" justify="center">
+    <v-progress-circular :indeterminate="true" color="neutral" />
   </v-row>
   </v-row>
 </template>
 </template>
 
 
-<script setup lang="ts">
-</script>
+<script setup lang="ts"></script>
 
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 38 - 36
components/Ui/SystemBar.vue

@@ -4,9 +4,11 @@ System bars
 
 
 <template>
 <template>
   <v-system-bar
   <v-system-bar
-      height="50"
-      :class="'d-flex flex-row justify-center align-center text-center ' + classes"
-      @click="onClick !== undefined ? onClick() : null"
+    height="50"
+    :class="
+      'd-flex flex-row justify-center align-center text-center ' + classes
+    "
+    @click="onClick !== undefined ? onClick() : null"
   >
   >
     <slot>
     <slot>
       <v-icon v-if="icon" small :icon="icon" />
       <v-icon v-if="icon" small :icon="icon" />
@@ -16,40 +18,40 @@ System bars
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  const props = defineProps({
-    text: {
-      type: String,
-      required: false,
-      default: ''
-    },
-    icon: {
-      type: String,
-      required: false,
-      default: undefined
-    },
-    backgroundColor: {
-      type: String,
-      required: false,
-      default: 'neutral-soft'
-    },
-    textColor: {
-      type: String,
-      required: false,
-      default: 'on-neutral-soft'
-    },
-    onClick: {
-      type: Function,
-      required: false,
-      default: undefined
-    },
-  })
+const props = defineProps({
+  text: {
+    type: String,
+    required: false,
+    default: '',
+  },
+  icon: {
+    type: String,
+    required: false,
+    default: undefined,
+  },
+  backgroundColor: {
+    type: String,
+    required: false,
+    default: 'neutral-soft',
+  },
+  textColor: {
+    type: String,
+    required: false,
+    default: 'on-neutral-soft',
+  },
+  onClick: {
+    type: Function,
+    required: false,
+    default: undefined,
+  },
+})
 
 
-  // TODO: voir si possible d'utiliser les variables sass à la place?
-  const classes = [
-    'bg-' + props.backgroundColor,
-    'text-' + props.textColor,
-    (props.onClick !== undefined ? 'clickable' : '')
-  ].join(' ')
+// TODO: voir si possible d'utiliser les variables sass à la place?
+const classes = [
+  'bg-' + props.backgroundColor,
+  'text-' + props.textColor,
+  props.onClick !== undefined ? 'clickable' : '',
+].join(' ')
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 6 - 15
components/Ui/Template/DataTable.vue

@@ -5,42 +5,33 @@ Template de base d'un tableau interactif
 -->
 -->
 
 
 <template>
 <template>
-  <v-col
-    cols="12"
-    sm="12"
-  >
-    <v-data-table
-      :headers="headersWithItem"
-      :items="items"
-      class="elevation-1"
-    >
+  <v-col cols="12" sm="12">
+    <v-data-table :headers="headersWithItem" :items="items" class="elevation-1">
       <template v-for="header in headersWithItem" #[header.item]="props">
       <template v-for="header in headersWithItem" #[header.item]="props">
         <slot :name="header.item" v-bind="props">
         <slot :name="header.item" v-bind="props">
           {{ props.item[header.value] }}
           {{ props.item[header.value] }}
         </slot>
         </slot>
       </template>
       </template>
-
     </v-data-table>
     </v-data-table>
   </v-col>
   </v-col>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-
 const props = defineProps({
 const props = defineProps({
   items: {
   items: {
     type: Array,
     type: Array,
-    required: true
+    required: true,
   },
   },
   headers: {
   headers: {
     type: Array,
     type: Array,
-    required: true
-  }
+    required: true,
+  },
 })
 })
 
 
 const { headers } = toRefs(props)
 const { headers } = toRefs(props)
 
 
 const headersWithItem = computed(() => {
 const headersWithItem = computed(() => {
-  return headers.value.map((header:any) => {
+  return headers.value.map((header: any) => {
     header.item = 'item.' + header.value
     header.item = 'item.' + header.value
     return header
     return header
   })
   })

+ 5 - 5
components/Ui/Template/Date.vue

@@ -7,16 +7,16 @@ Date formatée
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import DateUtils from "~/services/utils/dateUtils";
-import {computed} from "@vue/reactivity";
-import type {ComputedRef} from "@vue/reactivity";
+import DateUtils from '~/services/utils/dateUtils'
+import { computed } from '@vue/reactivity'
+import type { ComputedRef } from '@vue/reactivity'
 
 
 const props = defineProps({
 const props = defineProps({
   data: {
   data: {
     type: [String, Array],
     type: [String, Array],
     required: false,
     required: false,
-    default: null
-  }
+    default: null,
+  },
 })
 })
 
 
 const datesFormatted: ComputedRef<string> = computed(() => {
 const datesFormatted: ComputedRef<string> = computed(() => {

+ 39 - 44
components/Ui/Xeditable/Text.vue

@@ -8,69 +8,64 @@ Utilisé par exemple pour le choix de l'année active
   <main>
   <main>
     <!-- Mode édition activé -->
     <!-- Mode édition activé -->
     <div v-if="edit" class="d-flex align-center x-editable-input">
     <div v-if="edit" class="d-flex align-center x-editable-input">
-      <UiInputText
-          class="ma-0 pa-0"
-          :type="type"
-          v-model="inputValue"
-      />
+      <UiInputText class="ma-0 pa-0" :type="type" v-model="inputValue" />
 
 
       <v-icon
       <v-icon
-          icon="fas fa-check"
-          aria-hidden="false"
-          class="valid icons text-primary"
-          size="small"
-          @click="update"
+        icon="fas fa-check"
+        aria-hidden="false"
+        class="valid icons text-primary"
+        size="small"
+        @click="update"
       />
       />
       <v-icon
       <v-icon
-          icon="fas fa-times"
-          aria-hidden="false"
-          class="cancel icons text-neutral-strong"
-          size="small"
-          @click="close"
+        icon="fas fa-times"
+        aria-hidden="false"
+        class="cancel icons text-neutral-strong"
+        size="small"
+        @click="close"
       />
       />
     </div>
     </div>
 
 
     <!-- Mode édition désactivé -->
     <!-- Mode édition désactivé -->
-    <div v-else class="edit-link d-flex align-center" @click="edit=true">
-      <slot name="xeditable.read" v-bind="{inputValue}" />
+    <div v-else class="edit-link d-flex align-center" @click="edit = true">
+      <slot name="xeditable.read" v-bind="{ inputValue }" />
     </div>
     </div>
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import {ref} from "@vue/reactivity";
-import type {Ref} from "@vue/reactivity";
-
-  const props = defineProps({
-    type: {
-      type: String,
-      required: false,
-      default: null
-    },
-    data: {
-      type: [String, Number],
-      required: false,
-      default: null
-    }
-  })
+import { ref } from '@vue/reactivity'
+import type { Ref } from '@vue/reactivity'
 
 
-  const emit = defineEmits(['update'])
+const props = defineProps({
+  type: {
+    type: String,
+    required: false,
+    default: null,
+  },
+  data: {
+    type: [String, Number],
+    required: false,
+    default: null,
+  },
+})
 
 
-  const edit: Ref<boolean> = ref(false)
-  const inputValue: Ref<string|number|null> = ref(props.data)
+const emit = defineEmits(['update'])
 
 
-  const update = () => {
-    edit.value = false
-    if (inputValue.value !== props.data) {
-        emit('update', inputValue.value)
-    }
-  }
+const edit: Ref<boolean> = ref(false)
+const inputValue: Ref<string | number | null> = ref(props.data)
 
 
-  const close = () => {
-    edit.value = false
-    inputValue.value = 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
+}
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 87 - 90
composables/data/useAp2iRequestService.ts

@@ -1,109 +1,106 @@
-import {TYPE_ALERT} from "~/types/enum/enums";
-import ApiRequestService from "~/services/data/apiRequestService";
-import type {Ref} from "@vue/reactivity";
-import {usePageStore} from "~/stores/page";
-import UnauthorizedError from "~/services/error/UnauthorizedError";
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import type {AssociativeArray} from "~/types/data";
-import type {FetchContext, FetchOptions} from "ofetch";
+import type { Ref } from 'vue'
+import type { FetchContext, FetchOptions } from 'ofetch'
+import { TYPE_ALERT } from '~/types/enum/enums'
+import ApiRequestService from '~/services/data/apiRequestService'
+import { usePageStore } from '~/stores/page'
+import UnauthorizedError from '~/services/error/UnauthorizedError'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import type { AssociativeArray } from '~/types/data'
 
 
 /**
 /**
  * Retourne une instance de ApiRequestService configurée pour interroger l'api Ap2i
  * Retourne une instance de ApiRequestService configurée pour interroger l'api Ap2i
  *
  *
  * @see https://github.com/unjs/ohmyfetch/blob/main/README.md#%EF%B8%8F-create-fetch-with-default-options
  * @see https://github.com/unjs/ohmyfetch/blob/main/README.md#%EF%B8%8F-create-fetch-with-default-options
  */
  */
-let apiRequestServiceClass:null|ApiRequestService = null
+let apiRequestServiceClass: null | ApiRequestService = null
 export const useAp2iRequestService = () => {
 export const useAp2iRequestService = () => {
-    const runtimeConfig = useRuntimeConfig()
-
-    const baseURL = runtimeConfig.baseUrl ?? runtimeConfig.public.baseUrl
-
-    const pending: Ref<boolean> = ref(false)
-
-    /**
-     * Peuple les headers avant l'envoi de la requête
-     *
-     * @param request
-     * @param options
-     */
-    const onRequest = async function ({ request, options }: FetchContext) {
-        // @ts-ignore
-        if(options && options.noXaccessId) {
-            return
-        }
-
-        const accessProfileStore = useAccessProfileStore()
-
-        const headers: AssociativeArray = {
-            'x-accessid': String(accessProfileStore.id),
-            'Authorization': 'BEARER ' + accessProfileStore.bearer,
-        }
-
-        if (accessProfileStore.switchId) {
-            headers['x-switch-user'] = String(accessProfileStore.switchId)
-        }
+  const runtimeConfig = useRuntimeConfig()
+
+  const baseURL = runtimeConfig.baseUrl ?? runtimeConfig.public.baseUrl
+
+  const pending: Ref<boolean> = ref(false)
+
+  /**
+   * Peuple les headers avant l'envoi de la requête
+   *
+   * @param request
+   * @param options
+   */
+  const onRequest = function ({ request, options }: FetchContext) {
+    // @ts-expect-error options is not aware of noXaccessId
+    if (options && options.noXaccessId) {
+      return
+    }
 
 
-        options.headers = { ...options.headers, ...headers }
+    const accessProfileStore = useAccessProfileStore()
 
 
-        pending.value = true
-        console.log('Request : ' + request + ' (SSR: ' + process.server + ')')
+    const headers: AssociativeArray = {
+      'x-accessid': String(accessProfileStore.id),
+      Authorization: 'BEARER ' + accessProfileStore.bearer,
     }
     }
 
 
-    const onRequestError = async function({ request, options, response }: FetchContext) {
-        pending.value = false
+    if (accessProfileStore.switchId) {
+      headers['x-switch-user'] = String(accessProfileStore.switchId)
     }
     }
 
 
-    /**
-     * Server responded
-     *
-     * @param request
-     * @param options
-     * @param response
-     */
-    const onResponse = async function({ request, options, response }: FetchContext) {
-        pending.value = false
+    options.headers = { ...options.headers, ...headers }
+
+    pending.value = true
+    console.log('Request : ' + request + ' (SSR: ' + process.server + ')')
+  }
+
+  const onRequestError = function (_: FetchContext) {
+    pending.value = false
+  }
+
+  /**
+   * Server responded
+   */
+  const onResponse = function (_: FetchContext) {
+    pending.value = false
+  }
+
+  /**
+   * Gère les erreurs retournées par l'api
+   *
+   * @param request
+   * @param response
+   * @param error
+   */
+  const onResponseError = function ({ response, error }: FetchContext) {
+    pending.value = false
+
+    if (response && response.status === 401) {
+      throw new UnauthorizedError('Ap2i - Unauthorized')
+    } else if (response && response.status === 403) {
+      console.error('! Request error: Forbidden')
+      usePageStore().addAlert(TYPE_ALERT.ALERT, ['forbidden'])
+    } else if (
+      response &&
+      (response.status === 400 || response.status >= 404)
+    ) {
+      // @see https://developer.mozilla.org/fr/docs/Web/HTTP/Status
+      const errorMsg = error ? error.message : response.statusText
+      console.error('! Request error: ' + errorMsg)
+      usePageStore().addAlert(TYPE_ALERT.ALERT, [errorMsg])
     }
     }
+  }
 
 
-    /**
-     * Gère les erreurs retournées par l'api
-     *
-     * @param request
-     * @param response
-     * @param error
-     */
-    const onResponseError = async function ({ request, response, error }: FetchContext) {
-        pending.value = false
-
-        if (response && response.status === 401) {
-            throw new UnauthorizedError('Ap2i - Unauthorized')
-        }
-        else if (response && response.status === 403) {
-            console.error('! Request error: Forbidden')
-            usePageStore().addAlert(TYPE_ALERT.ALERT, ['forbidden'])
-        }
-        else if (response && (response.status === 400 || response.status >= 404)) {
-            // @see https://developer.mozilla.org/fr/docs/Web/HTTP/Status
-            const error_msg = error ? error.message : response.statusText
-            console.error('! Request error: ' + error_msg)
-            usePageStore().addAlert(TYPE_ALERT.ALERT, [error_msg])
-        }
-    }
+  const config: FetchOptions = {
+    baseURL,
+    onRequest,
+    onRequestError,
+    onResponse,
+    onResponseError,
+  }
 
 
-    const config : FetchOptions = {
-        baseURL,
-        onRequest,
-        onRequestError,
-        onResponse,
-        onResponseError
-    }
+  // Avoid memory leak
+  if (apiRequestServiceClass === null) {
+    // Utilise la fonction `create` d'ohmyfetch pour générer un fetcher dédié à l'interrogation de Ap2i
+    const fetcher = $fetch.create(config)
 
 
-    //Avoid memory leak
-    if (apiRequestServiceClass === null) {
-        // Utilise la fonction `create` d'ohmyfetch pour générer un fetcher dédié à l'interrogation de Ap2i
-        const fetcher = $fetch.create(config)
-        // @ts-ignore
-        apiRequestServiceClass = new ApiRequestService(fetcher)
-    }
+    apiRequestServiceClass = new ApiRequestService(fetcher)
+  }
 
 
-    return { apiRequestService: apiRequestServiceClass, pending: pending }
+  return { apiRequestService: apiRequestServiceClass, pending }
 }
 }

+ 46 - 30
composables/data/useEntityFetch.ts

@@ -1,42 +1,58 @@
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import ApiResource from "~/models/ApiResource";
-import type {AssociativeArray, Collection} from "~/types/data";
-import type {AsyncData} from "#app";
-import type {ComputedRef, Ref} from "vue";
-import {v4 as uuid4} from "uuid";
+import type { AsyncData } from '#app'
+import type { ComputedRef, Ref } from 'vue'
+import { v4 as uuid4 } from 'uuid'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import ApiResource from '~/models/ApiResource'
+import type { AssociativeArray, Collection } from '~/types/data'
 
 
 interface useEntityFetchReturnType {
 interface useEntityFetchReturnType {
-    fetch: (model: typeof ApiResource, id: number) => AsyncData<ApiResource, ApiResource | true>,
-    fetchCollection: (model: typeof ApiResource, parent?: ApiResource | null, query?: Ref<AssociativeArray>) => AsyncData<Collection, any>
-    // @ts-ignore
-    getRef: <T extends ApiResource>(model: typeof T, id: Ref<number | null>) => ComputedRef<null | T>
+  fetch: (
+    model: typeof ApiResource,
+    id: number,
+  ) => AsyncData<ApiResource | null, Error | null>
+
+  fetchCollection: (
+    model: typeof ApiResource,
+    parent?: ApiResource | null,
+    query?: Ref<AssociativeArray>,
+  ) => AsyncData<Collection | null, Error | null>
+
+  getRef: <T extends ApiResource>(
+    model: new () => T,
+    id: Ref<number | null>,
+  ) => ComputedRef<null | T>
 }
 }
 
 
 // TODO: améliorer le typage des fonctions sur le modèle de getRef
 // TODO: améliorer le typage des fonctions sur le modèle de getRef
-export const useEntityFetch = (lazy: boolean = false): useEntityFetchReturnType => {
-    const { em } = useEntityManager()
+export const useEntityFetch = (
+  lazy: boolean = false,
+): useEntityFetchReturnType => {
+  const { em } = useEntityManager()
 
 
-    const fetch = (model: typeof ApiResource, id: number) => useAsyncData(
-        model.entity + '_' + id + '_' + uuid4(),
-        () => em.fetch(model, id, true),
-        { lazy }
+  const fetch = (model: typeof ApiResource, id: number) =>
+    useAsyncData(
+      model.entity + '_' + id + '_' + uuid4(),
+      () => em.fetch(model, id, true),
+      { lazy },
     )
     )
 
 
-    const fetchCollection = (
-        model: typeof ApiResource,
-        parent: ApiResource | null = null,
-        query: Ref<AssociativeArray | null> = ref(null)
-    ) => useAsyncData(
-        model.entity + '_many_' + uuid4(),
-        () => em.fetchCollection(model, parent, query.value ?? undefined),
-        { lazy }
+  const fetchCollection = (
+    model: typeof ApiResource,
+    parent: ApiResource | null = null,
+    query: Ref<AssociativeArray | null> = ref(null),
+  ) =>
+    useAsyncData(
+      model.entity + '_many_' + uuid4(),
+      () => em.fetchCollection(model, parent, query.value ?? undefined),
+      { lazy },
     )
     )
 
 
-    // @ts-ignore
-    const getRef = <T extends ApiResource>(model: typeof T, id: Ref<number | null>): ComputedRef<T | null> => {
-        return computed(() => (id.value ? em.find(model, id.value) as T : null))
-    }
+  const getRef = <T extends ApiResource>(
+    model: new () => T,
+    id: Ref<number | null>,
+  ): ComputedRef<T | null> => {
+    return computed(() => (id.value ? (em.find(model, id.value) as T) : null))
+  }
 
 
-    //@ts-ignore
-    return { fetch, fetchCollection, getRef }
+  return { fetch, fetchCollection, getRef }
 }
 }

+ 9 - 9
composables/data/useEntityManager.ts

@@ -1,15 +1,15 @@
-import EntityManager from "~/services/data/entityManager";
-import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
-import {useRepo} from "pinia-orm";
+import { useRepo } from 'pinia-orm'
+import EntityManager from '~/services/data/entityManager'
+import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
 
 
 let entityManager: EntityManager | null = null
 let entityManager: EntityManager | null = null
 
 
 export const useEntityManager = () => {
 export const useEntityManager = () => {
-    if (entityManager === null) {
-        const { apiRequestService } = useAp2iRequestService()
-        const getRepo = useRepo
+  if (entityManager === null) {
+    const { apiRequestService } = useAp2iRequestService()
+    const getRepo = useRepo
 
 
-        entityManager = new EntityManager(apiRequestService, getRepo)
-    }
-    return { em: entityManager }
+    entityManager = new EntityManager(apiRequestService, getRepo)
+  }
+  return { em: entityManager }
 }
 }

+ 8 - 12
composables/data/useEnumFetch.ts

@@ -1,20 +1,16 @@
-import {useEnumManager} from "~/composables/data/useEnumManager";
-import type {Enum} from "~/types/data";
-import type {AsyncData} from "#app";
+import type { AsyncData } from '#app'
+import { useEnumManager } from '~/composables/data/useEnumManager'
+import type { Enum } from '~/types/data'
 
 
 interface useEnumFetchReturnType {
 interface useEnumFetchReturnType {
-    fetch: (enumName: string) => AsyncData<Enum, null | true | Error>,
+  fetch: (enumName: string) => AsyncData<Enum | null, Error | null>
 }
 }
 
 
 export const useEnumFetch = (lazy: boolean = false): useEnumFetchReturnType => {
 export const useEnumFetch = (lazy: boolean = false): useEnumFetchReturnType => {
-    const { enumManager } = useEnumManager()
+  const { enumManager } = useEnumManager()
 
 
-    const fetch = (enumName: string) => useAsyncData(
-        enumName,
-        () => enumManager.fetch(enumName),
-        { lazy }
-    )
+  const fetch = (enumName: string) =>
+    useAsyncData(enumName, () => enumManager.fetch(enumName), { lazy })
 
 
-    //@ts-ignore
-    return { fetch }
+  return { fetch }
 }
 }

+ 12 - 11
composables/data/useEnumManager.ts

@@ -1,15 +1,16 @@
-import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
-import EnumManager from "~/services/data/enumManager";
-import {useI18n} from "vue-i18n";
+import { useI18n } from 'vue-i18n'
+import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
+import EnumManager from '~/services/data/enumManager'
 
 
-let enumManager:EnumManager | null = null
+let enumManager: EnumManager | null = null
 
 
 export const useEnumManager = () => {
 export const useEnumManager = () => {
-    //Avoid memory leak
-    if (enumManager === null) {
-        const { apiRequestService } = useAp2iRequestService()
-        const i18n = useI18n() as any
-        enumManager = new EnumManager(apiRequestService, i18n)
-    }
-    return { enumManager: enumManager }
+  // Avoid memory leak
+  if (enumManager === null) {
+    const { apiRequestService } = useAp2iRequestService()
+    const i18n = useI18n()
+    // @ts-expect-error TODO: explain the error of conversion from useI18n result to VueI18n
+    enumManager = new EnumManager(apiRequestService, i18n)
+  }
+  return { enumManager }
 }
 }

+ 22 - 17
composables/data/useImageFetch.ts

@@ -1,29 +1,34 @@
-import {useImageManager} from "~/composables/data/useImageManager";
-import type {AsyncData, FetchResult} from "#app";
-import {v4 as uuid4} from "uuid";
-import type {Ref} from "@vue/reactivity";
+import type { AsyncData } from '#app'
+import { v4 as uuid4 } from 'uuid'
+import type { Ref } from 'vue'
+import { useImageManager } from '~/composables/data/useImageManager'
 
 
 interface useImageFetchReturnType {
 interface useImageFetchReturnType {
-    fetch: (id: Ref<number | null>, defaultImage?: string | null, height?: number, width?: number) => AsyncData<string | ArrayBuffer | null, Error | null>
+  fetch: (
+    id: Ref<number | null>,
+    defaultImage?: string | null,
+    height?: number,
+    width?: number,
+  ) => AsyncData<string | ArrayBuffer | null, Error | null>
 }
 }
 
 
 /**
 /**
  * Sert d'intermédiaire entre les composants et l'ImageManager en fournissant une méthode useAsyncData toute prête.
  * Sert d'intermédiaire entre les composants et l'ImageManager en fournissant une méthode useAsyncData toute prête.
  */
  */
 export const useImageFetch = (): useImageFetchReturnType => {
 export const useImageFetch = (): useImageFetchReturnType => {
-    const { imageManager } = useImageManager()
-
-    const fetch = (
-        id: Ref<number | null>,  // If id is null, fetch shall return the default image url
-        defaultImage: string | null = null,
-        height: number = 0,
-        width: number = 0
-    ) => useAsyncData(
-        'img' + (id ?? defaultImage ?? 0) + '_' + uuid4(),
-        () => imageManager.get(id.value, defaultImage, height, width),
-        { lazy: true, server: false },  // Always fetch images client-side
+  const { imageManager } = useImageManager()
 
 
+  const fetch = (
+    id: Ref<number | null>, // If id is null, fetch shall return the default image url
+    defaultImage: string | null = null,
+    height: number = 0,
+    width: number = 0,
+  ) =>
+    useAsyncData(
+      'img' + (id ?? defaultImage ?? 0) + '_' + uuid4(),
+      () => imageManager.get(id.value, defaultImage, height, width),
+      { lazy: true, server: false }, // Always fetch images client-side
     )
     )
 
 
-    return { fetch }
+  return { fetch }
 }
 }

+ 9 - 9
composables/data/useImageManager.ts

@@ -1,14 +1,14 @@
-import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
-import ImageManager from "~/services/data/imageManager";
+import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
+import ImageManager from '~/services/data/imageManager'
 
 
-let imageManager:ImageManager | null = null
+let imageManager: ImageManager | null = null
 
 
 export const useImageManager = () => {
 export const useImageManager = () => {
-    //Avoid memory leak
-    if (imageManager === null) {
-        const { apiRequestService } = useAp2iRequestService()
-        imageManager = new ImageManager(apiRequestService)
-    }
+  // Avoid memory leak
+  if (imageManager === null) {
+    const { apiRequestService } = useAp2iRequestService()
+    imageManager = new ImageManager(apiRequestService)
+  }
 
 
-    return { imageManager: imageManager }
+  return { imageManager }
 }
 }

+ 55 - 50
composables/data/useRefreshProfile.ts

@@ -1,61 +1,66 @@
-import {useEntityManager} from "~/composables/data/useEntityManager";
-import MyProfile from "~/models/Access/MyProfile";
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import {useOrganizationProfileStore} from "~/stores/organizationProfile";
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import MyProfile from '~/models/Access/MyProfile'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 
 
 export const useRefreshProfile = () => {
 export const useRefreshProfile = () => {
+  const accessProfileStore = useAccessProfileStore()
+  const organizationProfileStore = useOrganizationProfileStore()
+  const { em } = useEntityManager()
 
 
-    const accessProfileStore = useAccessProfileStore()
-    const organizationProfileStore = useOrganizationProfileStore()
-    const { em } = useEntityManager()
+  const fetchProfile = async (
+    accessId: number | null = null,
+  ): Promise<MyProfile> => {
+    if (accessId === null) {
+      accessId = accessProfileStore.currentAccessId
+    }
 
 
-    const fetchProfile = async (accessId: number | null = null): Promise<MyProfile> => {
-        if (accessId === null) {
-            accessId = accessProfileStore.currentAccessId
-        }
+    return (await em.fetch(MyProfile, accessId, true)) as MyProfile
+  }
 
 
-        return await em.fetch(MyProfile, accessId, true) as MyProfile
-    }
+  /**
+   * Fetch the access profile and initiate the user profile and organization profile stores
+   *
+   * /!\ Server side only!
+   *
+   * @param accessId
+   * @param bearer
+   * @param switchId
+   */
+  const initiateProfile = async (
+    accessId: number,
+    bearer: string,
+    switchId: number | null,
+  ): Promise<void> => {
+    accessProfileStore.$patch({
+      bearer,
+      id: accessId,
+      switchId,
+    })
 
 
-    /**
-     * Fetch the access profile and initiate the user profile and organization profile stores
-     *
-     * /!\ Server side only!
-     *
-     * @param accessId
-     * @param bearer
-     * @param switchId
-     */
-    const initiateProfile = async (accessId: number, bearer: string, switchId: number | null): Promise<void> => {
-        accessProfileStore.$patch({
-            bearer: bearer,
-            id: accessId,
-            switchId: switchId
-        })
-
-        const profile = await fetchProfile(accessId)
-
-        // Sans le flush, on observe un bug non-expliqué au rechargement de la page en mode dev : la fonction save
-        //  du repo de MyProfile ne fonctionne pas quand le plugin init.server.ts re-fetch le profil
-        em.flush(MyProfile)
-
-        accessProfileStore.initiateProfile(profile)
-        organizationProfileStore.initiateProfile(profile.organization)
-    }
+    const profile = await fetchProfile(accessId)
 
 
-    /**
-     * Re-fetch the user profile and update the store
-     */
-    const refreshProfile = async (accessId: number | null = null) => {
-        const profile = await fetchProfile(accessId)
+    // Sans le flush, on observe un bug non-expliqué au rechargement de la page en mode dev : la fonction save
+    //  du repo de MyProfile ne fonctionne pas quand le plugin init.server.ts re-fetch le profil
+    em.flush(MyProfile)
 
 
-        // Sans le flush, on observe un bug non-expliqué au rechargement de la page en mode dev : la fonction save
-        //  du repo de MyProfile ne fonctionne pas quand le plugin init.server.ts re-fetch le profil
-        em.flush(MyProfile)
+    accessProfileStore.initiateProfile(profile)
+    organizationProfileStore.initiateProfile(profile.organization)
+  }
 
 
-        accessProfileStore.setProfile(profile)
-        organizationProfileStore.setProfile(profile.organization)
-    }
+  /**
+   * Re-fetch the user profile and update the store
+   */
+  const refreshProfile = async (accessId: number | null = null) => {
+    const profile = await fetchProfile(accessId)
+
+    // Sans le flush, on observe un bug non-expliqué au rechargement de la page en mode dev : la fonction save
+    //  du repo de MyProfile ne fonctionne pas quand le plugin init.server.ts re-fetch le profil
+    em.flush(MyProfile)
+
+    accessProfileStore.setProfile(profile)
+    organizationProfileStore.setProfile(profile.organization)
+  }
 
 
-    return { initiateProfile, refreshProfile }
+  return { initiateProfile, refreshProfile }
 }
 }

+ 9 - 9
composables/form/useFieldViolation.ts

@@ -1,7 +1,7 @@
-import {computed} from "@vue/reactivity";
-import type {ComputedRef} from "@vue/reactivity";
-import {useFormStore} from "~/stores/form";
+import { computed } from 'vue'
+import type { ComputedRef } from 'vue'
 import * as _ from 'lodash-es'
 import * as _ from 'lodash-es'
+import { useFormStore } from '~/stores/form'
 
 
 /**
 /**
  * Composable pour gérer l'apparition de message d'erreurs de validation d'un champ de formulaire
  * Composable pour gérer l'apparition de message d'erreurs de validation d'un champ de formulaire
@@ -9,22 +9,22 @@ import * as _ from 'lodash-es'
  * @param field
  * @param field
  */
  */
 export function useFieldViolation(field: string) {
 export function useFieldViolation(field: string) {
-  const fieldViolations: ComputedRef<string> = computed(()=> {
+  const fieldViolations: ComputedRef<string> = computed(() => {
     return _.get(useFormStore().violations, field, '')
     return _.get(useFormStore().violations, field, '')
   })
   })
 
 
   /**
   /**
    * Lorsque la valeur d'un champ change, on supprime le fait qu'il puisse être "faux" dans le store
    * Lorsque la valeur d'un champ change, on supprime le fait qu'il puisse être "faux" dans le store
    * @param field
    * @param field
-   * @param value
    */
    */
-  function updateViolationState(field: string, value: any) {
-    //@ts-ignore
-    useFormStore().setViolations(_.omit(useFormStore().violations, field))
+  function updateViolationState(field: string) {
+    useFormStore().setViolations(
+      _.omit(useFormStore().violations, field) as string[],
+    )
   }
   }
 
 
   return {
   return {
     fieldViolations,
     fieldViolations,
-    updateViolationState: (fieldValue: any) => updateViolationState(fieldValue, field)
+    updateViolationState,
   }
   }
 }
 }

+ 20 - 11
composables/form/useValidation.ts

@@ -1,14 +1,13 @@
-import  {useI18n} from 'vue-i18n'
-import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
-import UrlUtils from "~/services/utils/urlUtils";
-import type {Ref} from "@vue/reactivity";
+import { useI18n } from 'vue-i18n'
+import type { Ref } from 'vue'
+import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
+import UrlUtils from '~/services/utils/urlUtils'
 
 
 /**
 /**
  * @category composables/form
  * @category composables/form
  * Composable pour des utils de verifications
  * Composable pour des utils de verifications
  */
  */
 export function useValidation() {
 export function useValidation() {
-
   /**
   /**
    * Use méthode fournissant une fonction pour tester la validité d'un Siret ainsi que la gestion du message d'erreur
    * Use méthode fournissant une fonction pour tester la validité d'un Siret ainsi que la gestion du message d'erreur
    */
    */
@@ -17,28 +16,38 @@ export function useValidation() {
     const siretErrorMessage: Ref<string> = ref('')
     const siretErrorMessage: Ref<string> = ref('')
 
 
     const validateSiret = async (siret: string) => {
     const validateSiret = async (siret: string) => {
-
       const { apiRequestService } = useAp2iRequestService()
       const { apiRequestService } = useAp2iRequestService()
-      const response: any = await apiRequestService.get(UrlUtils.join('/api/siret-checking', siret))
+      const response: Response = await apiRequestService.get(
+        UrlUtils.join('/api/siret-checking', siret),
+      )
 
 
       if (typeof response === 'undefined') {
       if (typeof response === 'undefined') {
         siretError.value = false
         siretError.value = false
         siretErrorMessage.value = ''
         siretErrorMessage.value = ''
       }
       }
 
 
+      if (!Object.prototype.hasOwnProperty.call(response, 'isCorrect')) {
+        throw new Error('Invalid response format')
+      }
+
+      // @ts-expect-error At this point, response has an 'isCorrect' property
+      const isCorrect = response.isCorrect
+
       const i18n = useI18n()
       const i18n = useI18n()
-      siretError.value = !response.isCorrect
-      siretErrorMessage.value = response.isCorrect ? '' : i18n.t('siret_error') as string
+      siretError.value = !isCorrect
+      siretErrorMessage.value = isCorrect
+        ? ''
+        : (i18n.t('siret_error') as string)
     }
     }
 
 
     return {
     return {
       siretError,
       siretError,
       siretErrorMessage,
       siretErrorMessage,
-      validateSiret
+      validateSiret,
     }
     }
   }
   }
 
 
   return {
   return {
-    useValidateSiret
+    useValidateSiret,
   }
   }
 }
 }

+ 2 - 3
composables/form/validation/useSubdomainValidation.ts

@@ -1,9 +1,8 @@
-import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
-import SubdomainValidation from "~/services/validation/subdomainValidation";
+import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
+import SubdomainValidation from '~/services/validation/subdomainValidation'
 
 
 let subdomainValidation: SubdomainValidation | null = null
 let subdomainValidation: SubdomainValidation | null = null
 
 
-
 export function useSubdomainValidation() {
 export function useSubdomainValidation() {
   if (subdomainValidation === null) {
   if (subdomainValidation === null) {
     const { apiRequestService } = useAp2iRequestService()
     const { apiRequestService } = useAp2iRequestService()

+ 13 - 8
composables/layout/useExtensionPanel.ts

@@ -1,4 +1,4 @@
-import type {Ref} from "@vue/reactivity";
+import type { Ref } from 'vue'
 import * as _ from 'lodash-es'
 import * as _ from 'lodash-es'
 
 
 /**
 /**
@@ -11,16 +11,21 @@ export function useExtensionPanel(route: Ref) {
 
 
   onMounted(() => {
   onMounted(() => {
     setTimeout(function () {
     setTimeout(function () {
-      _.each(document.getElementsByClassName('v-expansion-panel'), (element, index) => {
-        if (element.id == activeAccordionId) {
-          panel.value = index
-        }
-      })
-      if (!panel.value) { panel.value = 0 }
+      _.each(
+        document.getElementsByClassName('v-expansion-panel'),
+        (element, index) => {
+          if (element.id === activeAccordionId) {
+            panel.value = index
+          }
+        },
+      )
+      if (!panel.value) {
+        panel.value = 0
+      }
     }, 0)
     }, 0)
   })
   })
 
 
   return {
   return {
-    panel
+    panel,
   }
   }
 }
 }

+ 15 - 9
composables/layout/useMenu.ts

@@ -1,11 +1,11 @@
-import {useAccessProfileStore} from "~/stores/accessProfile";
-import {useAbility} from "@casl/vue";
-import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import type {MenuGroup, MenuItem} from "~/types/layout";
-import {MENU_LINK_TYPE} from "~/types/enum/layout";
-import type {AccessProfile} from "~/types/interfaces";
-import {useLayoutStore} from "~/stores/layout";
-import MenuComposer from "~/services/layout/menuComposer";
+import { useAbility } from '@casl/vue'
+import { useAccessProfileStore } from '~/stores/accessProfile'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
+import type { MenuGroup, MenuItem } from '~/types/layout'
+import { MENU_LINK_TYPE } from '~/types/enum/layout'
+import type { AccessProfile } from '~/types/interfaces'
+import { useLayoutStore } from '~/stores/layout'
+import MenuComposer from '~/services/layout/menuComposer'
 
 
 /**
 /**
  * Renvoie des méthodes pour interagir avec les menus
  * Renvoie des méthodes pour interagir avec les menus
@@ -31,7 +31,13 @@ export const useMenu = () => {
    * false, jusqu'à ce que je tilte que le menu s'appelait MyFamily, et pas Family
    * false, jusqu'à ce que je tilte que le menu s'appelait MyFamily, et pas Family
    */
    */
   const buildAllMenu = () => {
   const buildAllMenu = () => {
-    MenuComposer.build(runtimeConfig, ability, organizationProfile, accessProfile as AccessProfile, layoutState)
+    MenuComposer.build(
+      runtimeConfig,
+      ability,
+      organizationProfile,
+      accessProfile as AccessProfile,
+      layoutState,
+    )
   }
   }
 
 
   /**
   /**

+ 14 - 9
composables/utils/useAdminUrl.ts

@@ -1,14 +1,19 @@
-import UrlUtils from "~/services/utils/urlUtils";
+import UrlUtils from '~/services/utils/urlUtils'
 
 
 export const useAdminUrl = () => {
 export const useAdminUrl = () => {
-    const runtimeConfig = useRuntimeConfig()
+  const runtimeConfig = useRuntimeConfig()
 
 
-    const makeAdminUrl = (tail: string, query: Record<string, string> = {}): string => {
-        const baseUrl = runtimeConfig.baseUrlAdminLegacy ?? runtimeConfig.public.baseUrlAdminLegacy
-        let url = UrlUtils.join(baseUrl, '#', tail)
-        url = UrlUtils.addQuery(url, query)
-        return url
-    }
+  const makeAdminUrl = (
+    tail: string,
+    query: Record<string, string> = {},
+  ): string => {
+    const baseUrl =
+      runtimeConfig.baseUrlAdminLegacy ??
+      runtimeConfig.public.baseUrlAdminLegacy
+    let url = UrlUtils.join(baseUrl, '#', tail)
+    url = UrlUtils.addQuery(url, query)
+    return url
+  }
 
 
-    return { makeAdminUrl }
+  return { makeAdminUrl }
 }
 }

+ 14 - 11
composables/utils/useDownloadFile.ts

@@ -1,19 +1,22 @@
-import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
-import File from "~/models/Core/File"
-import FileSaver from "file-saver";
+import FileSaver from 'file-saver'
+import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
+import File from '~/models/Core/File'
 
 
 export const useDownloadFile = async (file: File) => {
 export const useDownloadFile = async (file: File) => {
-    const { apiRequestService } = useAp2iRequestService()
+  const { apiRequestService } = useAp2iRequestService()
 
 
-    const downloadUrl = `api/download/${file.id}`
+  const downloadUrl = `api/download/${file.id}`
 
 
-    const response: any = await apiRequestService.get(downloadUrl)
+  const response = await apiRequestService.get(downloadUrl)
 
 
-    if (!response || response.size === 0) {
-        console.error('Error: file ' + file.id + ' not found')
-    }
+  const blobPart = await response.blob()
 
 
-    const blob = new Blob([response], { type: response.type })
+  if (!response || blobPart.size === 0) {
+    console.error('Error: file ' + file.id + ' not found')
+  }
 
 
-    FileSaver.saveAs(blob, file.name ?? 'unknown');
+  const blob = new Blob([blobPart], { type: response.type })
+
+  // eslint-disable-next-line import/no-named-as-default-member
+  FileSaver.saveAs(blob, file.name ?? 'unknown')
 }
 }

+ 4 - 5
composables/utils/useHomeUrl.ts

@@ -1,10 +1,9 @@
-import {useAdminUrl} from "~/composables/utils/useAdminUrl";
+import { useAdminUrl } from '~/composables/utils/useAdminUrl'
 
 
 export const useHomeUrl = () => {
 export const useHomeUrl = () => {
+  const { makeAdminUrl } = useAdminUrl()
 
 
-    const { makeAdminUrl } = useAdminUrl()
+  const homeUrl = makeAdminUrl('dashboard')
 
 
-    const homeUrl = makeAdminUrl('dashboard')
-
-    return { homeUrl }
+  return { homeUrl }
 }
 }

+ 10 - 10
composables/utils/useI18nUtils.ts

@@ -1,13 +1,13 @@
-import {useI18n} from "vue-i18n";
-import I18nUtils from "~/services/utils/i18nUtils";
+import { useI18n } from 'vue-i18n'
+import I18nUtils from '~/services/utils/i18nUtils'
 
 
-let i18nUtilsClass:null|I18nUtils = null
+let i18nUtilsClass: null | I18nUtils = null
 export const useI18nUtils = () => {
 export const useI18nUtils = () => {
-    //Avoid memory leak
-    if(i18nUtilsClass === null){
-        const i18n = useI18n()
-        //@ts-ignore
-        i18nUtilsClass = new I18nUtils(i18n)
-    }
-    return i18nUtilsClass
+  // Avoid memory leak
+  if (i18nUtilsClass === null) {
+    const i18n = useI18n()
+    // @ts-expect-error TODO: explain the error of conversion from useI18n result to VueI18n
+    i18nUtilsClass = new I18nUtils(i18n)
+  }
+  return i18nUtilsClass
 }
 }

+ 17 - 13
composables/utils/useRedirect.ts

@@ -1,21 +1,25 @@
-import UrlUtils from "~/services/utils/urlUtils";
+import UrlUtils from '~/services/utils/urlUtils'
 
 
 export const useRedirect = () => {
 export const useRedirect = () => {
-    const runtimeConfig = useRuntimeConfig()
+  const runtimeConfig = useRuntimeConfig()
 
 
-    const redirectToLogout = () => {
-        if (!runtimeConfig.baseUrlAdminLegacy) {
-            throw new Error('Configuration error : no redirection target')
-        }
-        navigateTo(UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#/logout'), {external: true})
+  const redirectToLogout = () => {
+    if (!runtimeConfig.baseUrlAdminLegacy) {
+      throw new Error('Configuration error : no redirection target')
     }
     }
+    navigateTo(UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#/logout'), {
+      external: true,
+    })
+  }
 
 
-    const redirectToHome = () => {
-        if (!runtimeConfig.baseUrlAdminLegacy) {
-            throw new Error('Configuration error : no redirection target')
-        }
-        navigateTo(UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#/dashboard'), {external: true})
+  const redirectToHome = () => {
+    if (!runtimeConfig.baseUrlAdminLegacy) {
+      throw new Error('Configuration error : no redirection target')
     }
     }
+    navigateTo(UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#/dashboard'), {
+      external: true,
+    })
+  }
 
 
-    return { redirectToLogout, redirectToHome }
+  return { redirectToLogout, redirectToHome }
 }
 }

+ 8 - 8
composables/utils/useValidationUtils.ts

@@ -1,10 +1,10 @@
-import ValidationUtils from "~/services/utils/validationUtils";
+import ValidationUtils from '~/services/utils/validationUtils'
 
 
-let validationUtilsClass:null|ValidationUtils = null
+let validationUtilsClass: null | ValidationUtils = null
 export const useValidationUtils = () => {
 export const useValidationUtils = () => {
-    //Avoid memory leak
-    if(validationUtilsClass === null){
-        validationUtilsClass = new ValidationUtils()
-    }
-    return validationUtilsClass
-}
+  // Avoid memory leak
+  if (validationUtilsClass === null) {
+    validationUtilsClass = new ValidationUtils()
+  }
+  return validationUtilsClass
+}

+ 2 - 3
config/abilities/config.yaml

@@ -1,3 +1,2 @@
-abilities:
-  !!import/shallow
-    - pages/
+abilities: !!import/shallow
+  - pages/

+ 52 - 31
config/abilities/pages/addressBook.yaml

@@ -1,36 +1,57 @@
-  accesses_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Users']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'users'}]}
+accesses_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Users'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'users' }],
+      }
 
 
-  student_registration_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['UsersSchool']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'student-registration'}]}
+student_registration_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['UsersSchool'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'student-registration' }],
+      }
 
 
-  education_student_next_year_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['PedagogicsAdministation']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'educationstudent'}]}
+education_student_next_year_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['PedagogicsAdministation'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'educationstudent' }],
+      }
 
 
-  commissions_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Users']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'commissions'}]}
+commissions_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Users'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'commissions' }],
+      }
 
 
-  network_children_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Network']}
-      - {function: organizationHasChildren}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'network'}]}
+network_children_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Network'] }
+    - { function: organizationHasChildren }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'network' }],
+      }
 
 
-  network_parents_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['NetworkOrganization']}
-      - {function: organizationHasChildren, expectedResult: false}
+network_parents_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['NetworkOrganization'],
+      }
+    - { function: organizationHasChildren, expectedResult: false }

+ 40 - 31
config/abilities/pages/admin2ios.yaml

@@ -1,37 +1,46 @@
-  all_accesses_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Admin2IOS']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'user'}]}
+all_accesses_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Admin2IOS'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'user' }],
+      }
 
 
-  all_organizations_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Admin2IOS']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'organization'}]}
+all_organizations_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Admin2IOS'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'organization' }],
+      }
 
 
-  tips_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CorePremium']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'tips'}]}
+tips_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CorePremium'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'tips' }],
+      }
 
 
-  dgv_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Admin2IOS']}
+dgv_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Admin2IOS'] }
 
 
-  cmf_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Admin2IOS']}
+cmf_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Admin2IOS'] }
 
 
-  right_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Admin2IOS']}
+right_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Admin2IOS'] }
 
 
-  tree_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Admin2IOS']}
+tree_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Admin2IOS'] }

+ 88 - 46
config/abilities/pages/billing.yaml

@@ -1,53 +1,95 @@
-  billing_product_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['BillingAdministration']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'billings-administration'}]}
+billing_product_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['BillingAdministration'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'billings-administration' }],
+      }
 
 
-  billing_products_by_student_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['BillingAdministration']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'pedagogics-administration'}]}
+billing_products_by_student_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['BillingAdministration'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'pedagogics-administration' }],
+      }
 
 
-  billing_edition_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['BillingAdministration']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'manage', subject: 'billings-administration'}]}
+billing_edition_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['BillingAdministration'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'manage', subject: 'billings-administration' }],
+      }
 
 
-  billing_accounting_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['BillingAdministration']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'billings-administration'}]}
+billing_accounting_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['BillingAdministration'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'billings-administration' }],
+      }
 
 
-  billing_payment_list_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['BillingAdministration']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'billings-administration'}]}
+billing_payment_list_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['BillingAdministration'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'billings-administration' }],
+      }
 
 
-  pes_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Pes']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'manage', subject: 'billings-administration'}]}
+pes_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Pes'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'manage', subject: 'billings-administration' }],
+      }
 
 
-  berger_levrault_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['BergerLevrault']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'manage', subject: 'billings-administration'}]}
+berger_levrault_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['BergerLevrault'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'manage', subject: 'billings-administration' }],
+      }
 
 
-  jvs_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Jvs']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'manage', subject: 'billings-administration'}]}
-      -
-  afi_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['AFI']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'manage', subject: 'billings-administration'}]}
+jvs_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Jvs'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'manage', subject: 'billings-administration' }],
+      }
+    -
+afi_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['AFI'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'manage', subject: 'billings-administration' }],
+      }

+ 27 - 27
config/abilities/pages/communication.yaml

@@ -1,29 +1,29 @@
-  inbox_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['MessagesAdvanced']}
-      - function: accessHasAnyRoleAbility
-        parameters:
-          - {action: 'read', subject: 'mails'}
-          - {action: 'read', subject: 'emails'}
-          - {action: 'read', subject: 'texto'}
+inbox_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['MessagesAdvanced'] }
+    - function: accessHasAnyRoleAbility
+      parameters:
+        - { action: 'read', subject: 'mails' }
+        - { action: 'read', subject: 'emails' }
+        - { action: 'read', subject: 'texto' }
 
 
-  message_send_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['MessagesAdvanced']}
-      - function: accessHasAnyRoleAbility
-        parameters:
-          - {action: 'read', subject: 'mails'}
-          - {action: 'read', subject: 'emails'}
-          - {action: 'read', subject: 'texto'}
+message_send_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['MessagesAdvanced'] }
+    - function: accessHasAnyRoleAbility
+      parameters:
+        - { action: 'read', subject: 'mails' }
+        - { action: 'read', subject: 'emails' }
+        - { action: 'read', subject: 'texto' }
 
 
-  message_templates_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['MessagesAdvanced']}
-      - function: accessHasAnyRoleAbility
-        parameters:
-          - {action: 'read', subject: 'mails'}
-          - {action: 'read', subject: 'emails'}
-          - {action: 'read', subject: 'texto'}
+message_templates_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['MessagesAdvanced'] }
+    - function: accessHasAnyRoleAbility
+      parameters:
+        - { action: 'read', subject: 'mails' }
+        - { action: 'read', subject: 'emails' }
+        - { action: 'read', subject: 'texto' }

+ 176 - 101
config/abilities/pages/cotisations.yaml

@@ -1,101 +1,176 @@
-  rate_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationRate', 'CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  parameters_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  send_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  state_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  pay_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  check_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  ledger_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  magazine_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCMFAdministration']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  ventilated_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  pay_erase_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  resume_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationTransmissionState']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  history_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationCall']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  call_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationStructure']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  history_structure_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationStructure']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  insurance_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationStructure', 'CotisationTransmissionState']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  resume_all_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationTransmission']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
-
-  resume_pay_cotisation_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['CotisationTransmission']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'cotisation'}]}
+rate_cotisation_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['CotisationRate', 'CotisationCall'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+parameters_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+send_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+state_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+pay_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+check_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+ledger_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+magazine_cotisation_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['CotisationCMFAdministration'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+ventilated_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+pay_erase_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+resume_cotisation_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['CotisationTransmissionState'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+history_cotisation_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['CotisationCall'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+call_cotisation_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['CotisationStructure'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+history_structure_cotisation_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['CotisationStructure'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+insurance_cotisation_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['CotisationStructure', 'CotisationTransmissionState'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+resume_all_cotisation_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['CotisationTransmission'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }
+
+resume_pay_cotisation_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['CotisationTransmission'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'cotisation' }],
+      }

+ 8 - 5
config/abilities/pages/donor.yaml

@@ -1,5 +1,8 @@
-  donors_page:
-    action: 'display'
-    conditions:
-      - {function: organizationHasAnyModule, parameters: ['Donors']}
-      - {function: accessHasAnyRoleAbility, parameters: [{action: 'read', subject: 'donors'}]}
+donors_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['Donors'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'donors' }],
+      }

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