Browse Source

merge release/2.5

Olivier Massot 6 months ago
parent
commit
22b29f72c4
100 changed files with 772 additions and 702 deletions
  1. 0 58
      .eslintrc.cjs
  2. 16 0
      README.md
  3. 29 1
      assets/css/global.scss
  4. 6 6
      components/Layout/Alert/Container.vue
  5. 4 3
      components/Layout/Alert/Content.vue
  6. 16 10
      components/Layout/AlertBar.vue
  7. 46 43
      components/Layout/AlertBar/Cotisation.vue
  8. 1 1
      components/Layout/AlertBar/OnlineRegistration.vue
  9. 1 1
      components/Layout/AlertBar/RegistrationStatus.vue
  10. 3 3
      components/Layout/AlertBar/SuperAdmin.vue
  11. 3 3
      components/Layout/AlertBar/SwitchYear.vue
  12. 8 8
      components/Layout/Dialog.vue
  13. 5 2
      components/Layout/Dialog/Trial/AlreadyDid.vue
  14. 0 1
      components/Layout/Header.vue
  15. 1 1
      components/Layout/Header/HomeBtn.vue
  16. 9 10
      components/Layout/Header/Menu.vue
  17. 1 1
      components/Layout/Header/Notification.vue
  18. 6 3
      components/Layout/Header/Title.vue
  19. 2 2
      components/Layout/Header/UniversalCreation/Card.vue
  20. 4 5
      components/Layout/Header/UniversalCreation/CreateButton.vue
  21. 5 5
      components/Layout/Header/UniversalCreation/EventParams.vue
  22. 27 34
      components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue
  23. 7 7
      components/Layout/MainMenu.vue
  24. 2 0
      components/Layout/Pages/Subscription/Card.vue
  25. 3 3
      components/Layout/Pages/Subscription/List.vue
  26. 7 7
      components/Layout/Parameters/EntityTable.vue
  27. 8 13
      components/Layout/Parameters/Menu.vue
  28. 34 34
      components/Layout/Parameters/Table.vue
  29. 12 19
      components/Layout/Parameters/Website/ActivationSwitch.vue
  30. 4 4
      components/Layout/SubHeader/ActivityYear.vue
  31. 5 8
      components/Layout/SubHeader/Breadcrumbs.vue
  32. 7 6
      components/Layout/SubHeader/DataTiming.vue
  33. 4 4
      components/Layout/SubHeader/DataTimingRange.vue
  34. 6 6
      components/Layout/SubHeader/PersonnalizedList.vue
  35. 4 4
      components/Layout/Subheader.vue
  36. 2 2
      components/Layout/UpgradePremiumButton.vue
  37. 1 1
      components/Ui/Button/Delete.vue
  38. 16 15
      components/Ui/Button/Submit.vue
  39. 5 3
      components/Ui/Collection.vue
  40. 17 7
      components/Ui/DataTable.vue
  41. 32 29
      components/Ui/DatePicker.vue
  42. 20 14
      components/Ui/Form.vue
  43. 7 7
      components/Ui/Form/Creation.vue
  44. 9 11
      components/Ui/Form/DeletionConfirmationDialog.vue
  45. 9 9
      components/Ui/Form/Edition.vue
  46. 10 10
      components/Ui/Help.vue
  47. 27 12
      components/Ui/Image.vue
  48. 15 13
      components/Ui/Input/Autocomplete.vue
  49. 5 6
      components/Ui/Input/Autocomplete/Accesses.vue
  50. 16 12
      components/Ui/Input/AutocompleteWithAPI.vue
  51. 7 10
      components/Ui/Input/AutocompleteWithAp2i.vue
  52. 2 0
      components/Ui/Input/AutocompleteWithEnum.vue
  53. 0 2
      components/Ui/Input/Checkbox.vue
  54. 1 0
      components/Ui/Input/Combobox.vue
  55. 4 6
      components/Ui/Input/DatePicker.vue
  56. 2 2
      components/Ui/Input/Email.vue
  57. 4 4
      components/Ui/Input/Enum.vue
  58. 38 15
      components/Ui/Input/Image.vue
  59. 0 1
      components/Ui/Input/Number.vue
  60. 14 5
      components/Ui/Input/Phone.vue
  61. 6 4
      components/Ui/Input/Text.vue
  62. 4 4
      components/Ui/ItemFromUri.vue
  63. 1 2
      components/Ui/SystemBar.vue
  64. 15 5
      components/Ui/Template/DataTable.vue
  65. 2 2
      components/Ui/Template/Date.vue
  66. 3 3
      components/Ui/Xeditable/Text.vue
  67. 1 1
      composables/data/useAp2iRequestService.ts
  68. 1 1
      composables/data/useApiLegacyRequestService.ts
  69. 2 2
      composables/data/useEntityFetch.ts
  70. 4 5
      composables/data/useImageFetch.ts
  71. 1 2
      composables/utils/useDownloadFile.ts
  72. 0 2
      composables/utils/useDownloadFromRoute.ts
  73. 6 2
      composables/utils/useRedirect.ts
  74. 9 0
      config/abilities/pages/billing.yaml
  75. 33 0
      config/abilities/pages/parameters.yaml
  76. 2 2
      env/setupEnv.mjs
  77. 41 40
      eslint.config.mjs
  78. 8 2
      i18n/lang/fr.json
  79. 0 6
      layouts/.eslintrc.cjs
  80. 1 1
      layouts/error.vue
  81. 0 1
      layouts/parameters.vue
  82. 2 2
      models/Access/MyProfile.ts
  83. 0 1
      models/ApiResource.ts
  84. 1 1
      models/Core/Notification.ts
  85. 1 5
      models/Custom/Search/UserSearchItem.ts
  86. 1 1
      models/Organization/Parameters.ts
  87. 3 5
      models/decorators.ts
  88. 8 9
      nuxt.config.ts
  89. 2 4
      package.json
  90. 0 6
      pages/.eslintrc.cjs
  91. 0 1
      pages/parameters.vue
  92. 1 1
      pages/parameters/attendance_booking_reasons/new.vue
  93. 38 39
      pages/parameters/attendances.vue
  94. 1 4
      pages/parameters/bulletin.vue
  95. 1 4
      pages/parameters/education_notation.vue
  96. 1 1
      pages/parameters/education_timings/index.vue
  97. 1 1
      pages/parameters/education_timings/new.vue
  98. 8 7
      pages/parameters/general_parameters.vue
  99. 3 6
      pages/parameters/intranet.vue
  100. 1 5
      pages/parameters/sms.vue

+ 0 - 58
.eslintrc.cjs

@@ -1,58 +0,0 @@
-module.exports = {
-  root: true,
-  env: {
-    browser: true,
-    node: true,
-  },
-  parser: 'vue-eslint-parser',
-  parserOptions: {
-    ecmaVersion: 2020,
-    parser: '@typescript-eslint/parser',
-    sourceType: 'module',
-    tsconfigRootDir: __dirname,
-  },
-  extends: [
-    '@nuxtjs/eslint-config-typescript',
-    'plugin:nuxt/recommended',
-    'eslint:recommended',
-    'plugin:@typescript-eslint/recommended',
-    'plugin:vue/vue3-recommended',
-    'plugin:prettier/recommended',
-    'plugin:you-dont-need-lodash-underscore/compatible',
-  ],
-  ignorePatterns: [
-    '.nuxt',
-    'coverage/*',
-    'vendor/*',
-    'dist/*',
-    'models/models.ts',
-  ],
-  plugins: ['vue', '@typescript-eslint'],
-  // add your custom rules here
-  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,
-      },
-    ],
-    'vue/multi-word-component-names': 0,
-    '@typescript-eslint/no-inferrable-types': 0,
-  },
-  globals: {
-    useRuntimeConfig: 'readonly',
-    useAsyncData: 'readonly',
-    navigateTo: 'readonly',
-    computed: 'readonly',
-    ref: 'readonly',
-    definePageMeta: 'readonly',
-    useRouter: 'readonly',
-    useRoute: 'readonly',
-    useI18n: 'readonly',
-    onMounted: 'readonly',
-    onUnmounted: 'readonly',
-    watch: 'readonly',
-    useRepo: 'readonly',
-  },
-}

+ 16 - 0
README.md

@@ -67,6 +67,22 @@ Attention, sur les environnements de test, il faut utiliser nvm pour exécuter l
 
     nvm exec yarn install
 
+### Activer / désactiver le mode maintenance
+
+Pour activer le mode maintenance en production :
+
+    # (éditer les dates et heures de la maintenance, ligne 75)
+    nano /var/opentalent/git/app/public/maintenance.html
+
+    # activer la maintenance
+    touch /var/opentalent/git/app/.maintenance
+
+Pour le désactiver :
+
+    rm /var/opentalent/git/app/.maintenance
+
+> Les Ips internes sont exclues du mode maintenance
+
 ## Autres
 
 ### Lancer les tests

+ 29 - 1
assets/css/global.scss

@@ -71,7 +71,8 @@ header .v-toolbar__content {
   font-size: 0.9rem;
 }
 
-h3, h4 {
+h3,
+h4 {
   color: rgb(var(--v-theme-on-neutral-soft));
 }
 
@@ -84,3 +85,30 @@ h4 {
   font-size: 1.1rem;
   color: rgb(var(--v-theme-on-neutral));
 }
+
+// Encart informatif
+.explanation {
+  display: flex;
+  flex-direction: row;
+  margin: 32px;
+  padding: 8px 4px;
+  border-radius: 6px;
+  text-align: justify;
+  color: rgb(var(--v-theme-info));
+  border: solid 1px rgb(var(--v-theme-info));
+
+  .v-icon {
+    color: rgb(var(--v-theme-info));
+    font-size: 22px;
+    border-radius: 16px;
+    margin: 3px 1px;
+    padding: 3px;
+    height: 28px;
+    width: 28px;
+  }
+
+  @media (max-width: 600px) {
+    flex-direction: column;
+    justify-content: center;
+  }
+}

+ 6 - 6
components/Layout/Alert/Container.vue

@@ -1,13 +1,14 @@
 <!--
 Container principal pour l'affichage d'une ou plusieurs alertes
 -->
+<!-- eslint-disable vue/valid-v-for -->
 
 <template>
   <main class="alertContainer">
     <client-only>
       <LayoutAlertContent
-        v-for="(alert, key) in alerts"
-        :key="key"
+        v-for="(alert, index) in alerts"
+        :key="index"
         :alert="alert"
         class="alertContent"
       />
@@ -16,15 +17,14 @@ Container principal pour l'affichage d'une ou plusieurs alertes
 </template>
 
 <script setup lang="ts">
-import type { ComputedRef } from 'vue'
+import { computed } from 'vue'
 import type { Alert } from '~/types/interfaces'
 import { usePageStore } from '~/stores/page'
 
 const pageStore = usePageStore()
 
-const alerts: ComputedRef<Array<Alert>> = computed(() => {
-  return pageStore.alerts
-})
+// Using alerts in the template's v-for directive
+const alerts = computed<Array<Alert>>(() => pageStore.alerts)
 </script>
 
 <style scoped>

+ 4 - 3
components/Layout/Alert/Content.vue

@@ -1,4 +1,5 @@
 <!-- Message d'alerte -->
+<!-- eslint-disable vue/valid-v-for -->
 
 <template>
   <v-alert
@@ -47,7 +48,7 @@ const props = defineProps({
 })
 
 const show: Ref<boolean> = ref(true)
-let timeout: any = null
+let timeoutId: ReturnType<typeof setTimeout> | null = null
 
 const pageStore = usePageStore()
 
@@ -56,7 +57,7 @@ const pageStore = usePageStore()
  * @param time
  */
 const clearAlert = (time: number = 4000) => {
-  timeout = setTimeout(() => {
+  timeoutId = setTimeout(() => {
     show.value = false
     pageStore.removeSlowlyAlert()
   }, time)
@@ -66,7 +67,7 @@ const clearAlert = (time: number = 4000) => {
  * Réinitialise et suspend le délai avant le retrait de l'alerte au survol du curseur
  */
 const onMouseOver = () => {
-  clearTimeout(timeout)
+  clearTimeout(timeoutId)
 }
 
 /**

+ 16 - 10
components/Layout/AlertBar.vue

@@ -8,19 +8,21 @@ Contient les différentes barres d'alertes qui s'affichent dans certains cas
   <main>
     <v-expand-transition>
       <div v-if="showAlertBars">
-        <LayoutAlertBarEnv style="z-index: 510"/>
+        <LayoutAlertBarEnv style="z-index: 510" />
 
         <LayoutAlertBarSwitchUser style="z-index: 509" />
 
         <client-only>
           <LayoutAlertBarCotisation
-            v-if="organizationProfile.isCmf && ability.can('manage', 'cotisation')"
+            v-if="
+              organizationProfile.isCmf && ability.can('manage', 'cotisation')
+            "
             style="z-index: 508"
           />
         </client-only>
 
-        <LayoutAlertBarSwitchYear style="z-index: 507"/>
-        <LayoutAlertBarSuperAdmin style="z-index: 506"/>
+        <LayoutAlertBarSwitchYear style="z-index: 507" />
+        <LayoutAlertBarSuperAdmin style="z-index: 506" />
         <LayoutAlertBarRegistrationStatus
           v-if="organizationProfile.hasModule('IEL')"
           style="z-index: 505"
@@ -35,18 +37,23 @@ Contient les différentes barres d'alertes qui s'affichent dans certains cas
         @click="onFoldedWarningClick"
       >
         <v-icon small icon="fas fa-exclamation-triangle mx-1" />
-        <span>{{ $t("show_warnings") }}</span>
-        <v-icon small :icon="'fas mx-1' + (unfoldWarnings ? ' fa-chevron-up' : ' fa-chevron-down')" />
+        <span>{{ $t('show_warnings') }}</span>
+        <v-icon
+          small
+          :icon="
+            'fas mx-1' +
+            (unfoldWarnings ? ' fa-chevron-up' : ' fa-chevron-down')
+          "
+        />
       </div>
     </div>
   </main>
 </template>
 
 <script setup lang="ts">
-import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 import { useAbility } from '@casl/vue'
-import {useDisplay} from 'vuetify';
-import {StencilPreview} from 'vue-advanced-cropper';
+import { useDisplay } from 'vuetify'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 
 const organizationProfile = useOrganizationProfileStore()
 const ability = useAbility()
@@ -59,7 +66,6 @@ const onFoldedWarningClick = () => {
 }
 
 const showAlertBars = computed(() => mdAndUp.value || unfoldWarnings.value)
-
 </script>
 
 <style scoped lang="scss">

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

@@ -18,9 +18,8 @@ Barre d'alerte qui s'affiche pour donner l'état de la cotisation
 
 <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 type { ALERT_STATE_COTISATION } from '~/types/enum/enums'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import Cotisation from '~/models/Organization/Cotisation'
 
@@ -29,7 +28,51 @@ const organizationProfile = useOrganizationProfileStore()
 const runtimeConfig = useRuntimeConfig()
 const baseLegacyUrl: string = runtimeConfig.baseUrlAdminLegacy
 
-const cotisationYear: Ref<number | null> = ref(null)
+// On récupère l'état des cotisations via l'API
+if (!organizationProfile.id) {
+  throw new Error('missing organization id')
+}
+
+const { fetch } = useEntityFetch()
+const { data: cotisation, pending } = await fetch(
+  Cotisation,
+  organizationProfile.id,
+)
+
+interface Alert {
+  text: string
+  callback: () => void
+}
+
+const cotisationYear: ComputedRef<number | null> = computed(() => {
+  if (pending.value || cotisation.value === null) {
+    return null
+  }
+
+  return cotisation.value.cotisationYear
+})
+
+const alert: ComputedRef<Alert | null> = computed(() => {
+  if (pending.value || cotisation.value === null) {
+    return null
+  }
+
+  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,
+    },
+  }
+
+  if (!cotisation.value.alertState) {
+    return null
+  }
+
+  return mapping[cotisation.value.alertState as ALERT_STATE_COTISATION]
+})
 
 /**
  * Redirige l'utilisateur vers la page des cotisations
@@ -78,46 +121,6 @@ const openCmfSubscriptionPage = () => {
     '_blank',
   )
 }
-
-// On récupère l'état des cotisations via l'API
-if (!organizationProfile.id) {
-  throw new Error('missing organization id')
-}
-
-const { fetch } = useEntityFetch()
-const { data: cotisation, pending } = await fetch(
-  Cotisation,
-  organizationProfile.id,
-)
-
-interface Alert {
-  text: string
-  callback: () => void
-}
-
-const alert: ComputedRef<Alert | null> = computed(() => {
-  if (pending.value) {
-    return null
-  }
-
-  cotisationYear.value = cotisation.value.cotisationYear
-
-  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,
-    },
-  }
-
-  if (!cotisation.value.alertState) {
-    return null
-  }
-
-  return mapping[cotisation.value.alertState as ALERT_STATE_COTISATION]
-})
 </script>
 
 <style scoped lang="scss">

+ 1 - 1
components/Layout/AlertBar/OnlineRegistration.vue

@@ -13,10 +13,10 @@ Barre d'alerte sur l'ouverture ou non de l'inscription en ligne
 </template>
 
 <script setup lang="ts">
+import type { ComputedRef } from 'vue'
 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()
 

+ 1 - 1
components/Layout/AlertBar/RegistrationStatus.vue

@@ -13,10 +13,10 @@ Barre d'alerte quand au statut (l'avancement) de l'inscription en ligne de l'uti
 </template>
 
 <script setup lang="ts">
+import type { ComputedRef } from 'vue'
 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()
 

+ 3 - 3
components/Layout/AlertBar/SuperAdmin.vue

@@ -13,10 +13,10 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur est un super admin en mode sw
 </template>
 
 <script setup lang="ts">
+import type { ComputedRef } from 'vue'
+import { navigateTo } from '#app'
 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()
@@ -37,7 +37,7 @@ const url: ComputedRef<string> = computed(() => {
     ? accessProfile.originalAccess.id
     : null
 
-  if (show && orgId && originalAccessId) {
+  if (show.value && orgId && originalAccessId) {
     return makeAdminUrl(
       UrlUtils.join('switch_user', orgId, originalAccessId, 'exit'),
     )

+ 3 - 3
components/Layout/AlertBar/SwitchYear.vue

@@ -10,9 +10,9 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur n'est pas sur l'année couran
     {{ $t('not_current_year') }}
 
     <a
-      @click="resetYear"
       class="text-decoration-none on-warning"
       style="cursor: pointer"
+      @click="resetYear"
     >
       <strong class="pl-2 text-neutral-strong">
         {{ $t('not_current_year_reset') }}
@@ -22,9 +22,9 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur n'est pas sur l'année couran
 </template>
 
 <script setup lang="ts">
+import type { ComputedRef } from 'vue'
 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'
@@ -65,7 +65,7 @@ const resetYear = async () => {
 
   pageStore.loading = true
   await em.patch(Access, accessProfile.currentAccessId, defaultValues)
-  if (process.server) {
+  if (import.meta.server) {
     // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
     await refreshProfile()
   }

+ 8 - 8
components/Layout/Dialog.vue

@@ -13,9 +13,9 @@
           theme
         "
       >
-        <h3 class="d-flex">
-          <v-icon icon="fa-solid fa-bullhorn" />
-          <slot name="dialogType" />
+        <h3 :class="'d-flex theme-' + theme">
+          <v-icon icon="fa-solid fa-bullhorn" width="25" htight="25" />
+          <span class="pt-4"><slot name="dialogType" /></span>
         </h3>
       </div>
 
@@ -48,6 +48,7 @@ const props = defineProps({
   contentClass: {
     type: String,
     required: false,
+    default: '',
   },
   theme: {
     type: String,
@@ -61,13 +62,13 @@ const props = defineProps({
   },
 })
 
-// @ts-ignore  -> just to avoid the error with the prop's type of v-dialog
+// @ts-expect-error This is just to avoid the error with the prop's type of v-dialog
 const _show = computed(() => props.show) as boolean
 </script>
 
 <style lang="scss" scoped>
 .dialog-title {
-  padding-left: 40px;
+  padding-left: 16px;
   font-weight: normal;
 }
 
@@ -79,17 +80,16 @@ const _show = computed(() => props.show) as boolean
   padding: 25px 10px;
 
   h3 {
-    font-size: 25px;
     font-weight: normal;
     writing-mode: vertical-lr;
     transform: rotate(-180deg);
+    padding-left: 6px;
   }
 
   .v-icon {
     font-size: 25px;
     transform: rotate(90deg);
-    padding-right: 20px;
-    padding-bottom: 10px;
+    padding-right: 25px;
   }
 }
 

+ 5 - 2
components/Layout/Dialog/Trial/AlreadyDid.vue

@@ -28,7 +28,10 @@
       <v-btn class="mr-4 submitBtn theme-warning" @click="goSubscribe">
         {{ $t('i_subscribe') }}
       </v-btn>
-      <v-btn class="mr-4 submitBtn theme-neutral-strong" @click="contactOpentalent">
+      <v-btn
+        class="mr-4 submitBtn theme-neutral-strong"
+        @click="contactOpentalent"
+      >
         {{ $t('opentalent_contact') }}
       </v-btn>
     </template>
@@ -36,7 +39,7 @@
 </template>
 
 <script setup lang="ts">
-import UrlUtils from "~/services/utils/urlUtils";
+import UrlUtils from '~/services/utils/urlUtils'
 
 const runtimeConfig = useRuntimeConfig()
 

+ 0 - 1
components/Layout/Header.vue

@@ -116,7 +116,6 @@ const layoutStore = useLayoutStore()
 </script>
 
 <style scoped>
-
 :deep(.v-toolbar__content > .v-toolbar-title) {
   margin-left: 2px;
 

+ 1 - 1
components/Layout/Header/HomeBtn.vue

@@ -12,7 +12,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from '@vue/reactivity'
+import { ref } from 'vue'
 import { useDisplay } from 'vuetify'
 import { useHomeUrl } from '~/composables/utils/useHomeUrl'
 

+ 9 - 10
components/Layout/Header/Menu.vue

@@ -16,7 +16,7 @@ header principal (configuration, paramètres du compte...)
           :defaultImage="menu.icon.avatarByDefault"
           :width="30"
         /> -->
-        <UiImage :defaultImage="menu.icon.avatarByDefault" :width="30" />
+        <UiImage :default-image="menu.icon.avatarByDefault" :width="30" />
       </v-avatar>
 
       <v-icon v-else :icon="menu.icon.name" class="on-primary" />
@@ -27,7 +27,7 @@ header principal (configuration, paramètres du compte...)
     <v-menu
       :activator="btn"
       :model-value="isOpened()"
-      @update:modelValue="onStateUpdated"
+      @update:model-value="onStateUpdated"
     >
       <v-card>
         <v-card-title class="theme-neutral text-body-2 font-weight-bold">
@@ -48,8 +48,8 @@ header principal (configuration, paramètres du compte...)
                     size="30"
                   >
                     <UiImage
-                      :imageId="child.icon.avatarId"
-                      :defaultImage="child.icon.avatarByDefault"
+                      :image-id="child.icon.avatarId"
+                      :default-image="child.icon.avatarByDefault"
                       :width="30"
                     />
                   </v-avatar>
@@ -76,10 +76,9 @@ header principal (configuration, paramètres du compte...)
               :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">
+                {{ $t(action.label) }}
+              </v-list-item-title>
             </v-list-item>
           </template>
         </v-card-actions>
@@ -89,8 +88,8 @@ header principal (configuration, paramètres du compte...)
 </template>
 
 <script setup lang="ts">
+import { computed, ref } from 'vue'
 import { useMenu } from '~/composables/layout/useMenu'
-import { computed, ref } from '@vue/reactivity'
 
 const props = defineProps({
   name: {
@@ -111,7 +110,7 @@ const menu = getMenu(props.name)
 const displayMenu = computed(() => hasMenu(props.name))
 const isOpened = () => isMenuOpened(props.name)
 
-const onStateUpdated = (e: any) => {
+const onStateUpdated = (e: boolean) => {
   setMenuState(props.name, e)
 }
 

+ 1 - 1
components/Layout/Header/Notification.vue

@@ -48,7 +48,7 @@
             </template>
           </v-list-item>
 
-          <v-divider></v-divider>
+          <v-divider />
 
           <!--suppress VueUnrecognizedDirective -->
           <span v-intersect="onLastNotificationIntersect" />

+ 6 - 3
components/Layout/Header/Title.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="d-flex flex-row">
-    <a :href="homeUrl" :title="$t('go_back_home')" class="d-flex flex-row align-center">
+    <a
+      :href="homeUrl"
+      :title="$t('go_back_home')"
+      class="d-flex flex-row align-center"
+    >
       <v-img src="/favicon.ico" height="42" width="42" class="mr-2" />
       <span v-if="mdAndUp"><slot /></span>
     </a>
@@ -8,12 +12,11 @@
 </template>
 
 <script setup lang="ts">
-import { useHomeUrl } from '~/composables/utils/useHomeUrl'
 import { useDisplay } from 'vuetify'
+import { useHomeUrl } from '~/composables/utils/useHomeUrl'
 
 const { homeUrl } = useHomeUrl()
 const { mdAndUp } = useDisplay()
-
 </script>
 
 <style scoped lang="scss">

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

@@ -43,11 +43,11 @@
 </template>
 
 <script setup lang="ts">
-import type { PropType } from '@vue/runtime-core'
+import type { PropType } from 'vue'
+import { useDisplay } from 'vuetify'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
 import { useAdminUrl } from '~/composables/utils/useAdminUrl'
 import UrlUtils from '~/services/utils/urlUtils'
-import {useDisplay} from 'vuetify';
 
 const props = defineProps({
   /**

+ 4 - 5
components/Layout/Header/UniversalCreation/CreateButton.vue

@@ -49,8 +49,8 @@
       <template #dialogText>
         <LayoutHeaderUniversalCreationGenerateCardsSteps
           :path="path"
-          @cardClick="onCardClick"
-          @urlUpdate="onUrlUpdate"
+          @card-click="onCardClick"
+          @url-update="onUrlUpdate"
         />
       </template>
 
@@ -82,10 +82,9 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from '@vue/reactivity'
-import type { Ref } from '@vue/reactivity'
+import { ref } from 'vue'
+import type { Ref, ComputedRef } from 'vue'
 import { useDisplay } from 'vuetify'
-import type { ComputedRef } from 'vue'
 import { usePageStore } from '~/stores/page'
 
 const { mdAndDown: asIcon } = useDisplay()

+ 5 - 5
components/Layout/Header/UniversalCreation/EventParams.vue

@@ -15,7 +15,7 @@ Event parameters page in the create dialog
     </v-row>
 
     <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 cols="9" class="pt-1">
         <i class="fa fa-circle-info" />
         {{
@@ -54,11 +54,11 @@ Event parameters page in the create dialog
 </template>
 
 <script setup lang="ts">
-import { ref } from '@vue/reactivity'
-import type { Ref } from '@vue/reactivity'
+import { ref } from 'vue'
+import type { Ref, ComputedRef } from 'vue'
 import { add, format, startOfHour, formatISO } from 'date-fns'
-import type { ComputedRef } from 'vue'
-import DateUtils, { supportedLocales } from '~/services/utils/dateUtils'
+import type { supportedLocales } from '~/services/utils/dateUtils'
+import DateUtils from '~/services/utils/dateUtils'
 
 const i18n = useI18n()
 

+ 27 - 34
components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue

@@ -11,41 +11,41 @@
         <v-container v-if="location === 'home'">
           <v-row>
             <!-- Une personne -->
-            <v-col cols="12" md="6" v-if="ability.can('manage', 'users')">
+            <v-col v-if="ability.can('manage', 'users')" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
-                @click="onCardClick('access')"
                 title="a_person"
                 text-content="add_new_person_student"
                 icon="fa fa-user"
+                @click="onCardClick('access')"
               />
             </v-col>
             <v-col
-              cols="12"
-              md="6"
               v-if="
                 ability.can('display', 'agenda_page') &&
                 (ability.can('display', 'course_page') ||
                   ability.can('display', 'exam_page') ||
                   ability.can('display', 'pedagogics_project_page'))
               "
+              cols="12"
+              md="6"
             >
               <!-- Un évènement -->
               <LayoutHeaderUniversalCreationCard
-                @click="onCardClick('event')"
                 title="an_event"
                 text-content="add_an_event_course"
                 icon="fa fa-calendar"
+                @click="onCardClick('event')"
               />
             </v-col>
 
             <!-- Autre évènement -->
             <v-col
-              cols="12"
-              md="6"
               v-else-if="
                 ability.can('display', 'agenda_page') &&
                 ability.can('manage', 'events')
               "
+              cols="12"
+              md="6"
             >
               <LayoutHeaderUniversalCreationCard
                 to="event-params"
@@ -59,29 +59,25 @@
 
             <!-- Une correspondance -->
             <v-col
-              cols="12"
-              md="6"
               v-if="
                 ability.can('display', 'message_send_page') &&
                 (ability.can('manage', 'emails') ||
                   ability.can('manage', 'mails') ||
                   ability.can('manage', 'texto'))
               "
+              cols="12"
+              md="6"
             >
               <LayoutHeaderUniversalCreationCard
-                @click="onCardClick('message')"
                 title="a_correspondence"
                 text-content="send_email_letter"
                 icon="fa fa-envelope"
+                @click="onCardClick('message')"
               />
             </v-col>
 
             <!-- Un matériel (direct link) -->
-            <v-col
-              cols="12"
-              md="6"
-              v-if="ability.can('manage', 'equipments')"
-            >
+            <v-col v-if="ability.can('manage', 'equipments')" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_materiel"
                 text-content="add_any_type_material"
@@ -99,11 +95,7 @@
         <v-container v-if="location === 'access'">
           <v-row>
             <!-- Un adhérent -->
-            <v-col
-              cols="12"
-              md="6"
-              v-if="isLaw1901"
-            >
+            <v-col v-if="isLaw1901" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="an_adherent"
                 text-content="adherent_text_creation_card"
@@ -114,11 +106,7 @@
             </v-col>
 
             <!-- Un membre du CA -->
-            <v-col
-              cols="12"
-              md="6"
-              v-if="isLaw1901"
-            >
+            <v-col v-if="isLaw1901" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_ca_member"
                 text-content="ca_member_text_creation_card"
@@ -184,7 +172,7 @@
             </v-col>
 
             <!-- Une inscription en ligne -->
-            <v-col cols="12" md="6" v-if="hasOnlineRegistrationModule">
+            <v-col v-if="hasOnlineRegistrationModule" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="online_registration"
                 text-content="online_registration_text_creation_card"
@@ -211,7 +199,11 @@
         <v-container v-if="location === 'event'">
           <v-row>
             <!-- Un cours -->
-            <v-col cols="12" md="6" v-if="ability.can('display', 'course_page')">
+            <v-col
+              v-if="ability.can('display', 'course_page')"
+              cols="12"
+              md="6"
+            >
               <LayoutHeaderUniversalCreationCard
                 title="course"
                 text-content="course_text_creation_card"
@@ -222,7 +214,7 @@
             </v-col>
 
             <!-- Un examen -->
-            <v-col cols="12" md="6" v-if="ability.can('display', 'exam_page')">
+            <v-col v-if="ability.can('display', 'exam_page')" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="exam"
                 text-content="exam_text_creation_card"
@@ -234,8 +226,9 @@
 
             <!-- Un projet pédagogique -->
             <v-col
-              cols="12" md="6"
               v-if="ability.can('display', 'pedagogics_project_page')"
+              cols="12"
+              md="6"
             >
               <LayoutHeaderUniversalCreationCard
                 title="educational_services"
@@ -247,7 +240,7 @@
             </v-col>
 
             <!-- Un autre évènement -->
-            <v-col cols="12" md="6" v-if="ability.can('manage', 'events')">
+            <v-col v-if="ability.can('manage', 'events')" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 to="event-params"
                 href="/calendar/create/events"
@@ -264,7 +257,7 @@
         <v-container v-if="location === 'message'">
           <v-row>
             <!-- Un email -->
-            <v-col cols="12" md="6" v-if="ability.can('manage', 'emails')">
+            <v-col v-if="ability.can('manage', 'emails')" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="an_email"
                 text-content="email_text_creation_card"
@@ -275,7 +268,7 @@
             </v-col>
 
             <!-- Un courrier -->
-            <v-col cols="12" md="6" v-if="ability.can('manage', 'mails')">
+            <v-col v-if="ability.can('manage', 'mails')" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_letter"
                 text-content="letter_text_creation_card"
@@ -286,7 +279,7 @@
             </v-col>
 
             <!-- Un SMS -->
-            <v-col cols="12" md="6" v-if="ability.can('manage', 'texto')">
+            <v-col v-if="ability.can('manage', 'texto')" cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_sms"
                 text-content="sms_text_creation_card"
@@ -311,8 +304,8 @@
 </template>
 <script setup lang="ts">
 import { ref, computed } from 'vue'
-import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 import { useAbility } from '@casl/vue'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 
 const props = defineProps({
   /**

+ 7 - 7
components/Layout/MainMenu.vue

@@ -60,10 +60,10 @@ Prend en paramètre une liste de ItemMenu et les met en forme
           </template>
 
           <v-list-item
-            v-for="child in item.children"
-            :key="child.label"
-            :title="$t(child.label)"
+            v-for="(child, index) in item.children"
             :id="'main-menu-item' + item.label + '-' + child.label"
+            :key="index"
+            :title="$t(child.label)"
             :prepend-icon="child.icon.name"
             :href="!isInternalLink(child) ? child.to : undefined"
             :to="isInternalLink(child) ? child.to : undefined"
@@ -82,9 +82,9 @@ Prend en paramètre une liste de ItemMenu et les met en forme
 </template>
 
 <script setup lang="ts">
-import { useMenu } from '~/composables/layout/useMenu'
-import { computed } from '@vue/reactivity'
+import { computed } from 'vue'
 import { useDisplay } from 'vuetify'
+import { useMenu } from '~/composables/layout/useMenu'
 import type { MenuGroup, MenuItem } from '~/types/layout'
 import { useApiLegacyRequestService } from '~/composables/data/useApiLegacyRequestService'
 
@@ -119,7 +119,7 @@ const isRail = computed(() => {
 
 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é.
-  if (process.client && menu !== null) {
+  if (import.meta.client && menu !== null) {
     setMenuState('Main', lgAndUp.value)
   }
 })
@@ -139,7 +139,7 @@ function getItems(
 
   if (menu === null) {
     items = []
-  } else if (menu.hasOwnProperty('children')) {
+  } else if (Object.prototype.hasOwnProperty.call(menu, 'children')) {
     items = (menu as MenuGroup).children ?? []
   } else {
     items = [menu]

+ 2 - 0
components/Layout/Pages/Subscription/Card.vue

@@ -46,10 +46,12 @@ const props = defineProps({
   subTitle: {
     type: String,
     required: false,
+    default: '',
   },
   extraHeader: {
     type: String,
     required: false,
+    default: '',
   },
   color: {
     type: String,

+ 3 - 3
components/Layout/Pages/Subscription/List.vue

@@ -1,19 +1,19 @@
 <template>
   <ul>
-    <li v-for="li in elements">
+    <li v-for="(li, index) in elements" :key="index">
       <v-icon
         class="check"
         :color="color"
         icon="fa-solid fa-check"
         size="large"
-      ></v-icon>
+      />
       <span class="pl-2">{{ li }}</span>
     </li>
   </ul>
 </template>
 
 <script setup lang="ts">
-const props = defineProps({
+defineProps({
   elements: {
     type: Array,
     required: true,

+ 7 - 7
components/Layout/Parameters/EntityTable.vue

@@ -11,15 +11,15 @@ A data table for the parameters page
         :columns-definitions="columns"
         :actions="actions"
         :actions-route="actionsRoute"
-        @editClicked="onEditClicked"
-        @deleteClicked="onDeleteClicked"
-        @addClicked="goToCreatePage"
+        @edit-clicked="onEditClicked"
+        @delete-clicked="onDeleteClicked"
+        @add-clicked="goToCreatePage"
       />
 
       <UiFormDeletionConfirmationDialog
         v-model="showDeletionConfirmationDialog"
-        @deleteClicked="onDeleteConfirmed"
-        @cancelClicked="onCancelClicked"
+        @delete-clicked="onDeleteConfirmed"
+        @cancel-clicked="onCancelClicked"
       />
     </div>
   </div>
@@ -47,7 +47,7 @@ const props = defineProps({
   title: {
     type: String,
     required: false,
-    default: null
+    default: null,
   },
   /**
    * If provided, define the columns to show.
@@ -69,7 +69,7 @@ const props = defineProps({
   actions: {
     type: Array as PropType<Array<TABLE_ACTION>>,
     required: false,
-    default: [TABLE_ACTION.EDIT, TABLE_ACTION.DELETE, TABLE_ACTION.ADD],
+    default: () => [TABLE_ACTION.EDIT, TABLE_ACTION.DELETE, TABLE_ACTION.ADD],
   },
   /**
    * The base URL for the edit / create pages

+ 8 - 13
components/Layout/Parameters/Menu.vue

@@ -31,8 +31,7 @@
         :prepend-icon="item.icon ? item.icon.name : ''"
         :to="(item as MenuItem).to"
         @click="onItemClicked"
-      >
-      </v-list-item>
+      />
     </v-list>
   </v-navigation-drawer>
 </template>
@@ -54,17 +53,15 @@ const isOpened = computed(() => isMenuOpened('Parameters'))
 
 // En vue lg+, on affiche toujours le menu
 const displayMenu = computed(() => {
-  return menu !== null && hasMenu('Parameters') && (lgAndUp.value || isOpened.value)
+  return (
+    menu !== null && hasMenu('Parameters') && (lgAndUp.value || isOpened.value)
+  )
 })
 
 // En vue md+, fermer le menu le passe simplement en mode rail
 // Sinon, le fermer le masque complètement
 const isRail = computed(() => {
-  return (
-    menu !== null &&
-    mdAndUp.value &&
-    !isOpened.value
-  )
+  return menu !== null && mdAndUp.value && !isOpened.value
 })
 
 const onItemClicked = () => {
@@ -75,7 +72,7 @@ const onItemClicked = () => {
 
 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) {
+  if (import.meta.client && menu !== null) {
     setMenuState('Parameters', mdAndUp.value)
   }
 })
@@ -90,15 +87,14 @@ onUnmounted(() => {
 </script>
 
 <style scoped lang="scss">
-
 .parameters-menu {
   position: relative;
   overflow: hidden;
 }
 
 .parameters-menu::before {
-  content: "\f013";
-  font-family: "Font Awesome 6 Free",serif;
+  content: '\f013';
+  font-family: 'Font Awesome 6 Free', serif;
   font-weight: 900;
   font-size: 300px;
   color: rgb(var(--v-theme-neutral-soft));
@@ -110,7 +106,6 @@ onUnmounted(() => {
   user-select: none;
 }
 
-
 .title {
   display: flex;
   flex-direction: row;

+ 34 - 34
components/Layout/Parameters/Table.vue

@@ -12,34 +12,31 @@ A data table for the parameters page
     <v-table>
       <thead>
         <tr>
-          <td v-for="col in columns">
+          <td v-for="(col, index) in columns" :key="index">
             {{ col.label }}
           </td>
           <td>{{ $t('actions') }}</td>
         </tr>
       </thead>
       <tbody v-if="items.length > 0">
-        <tr
-          v-for="(item, i) in items"
-          :key="i"
-        >
+        <tr v-for="(item, i) in items" :key="i">
           <td
-            v-for="col in columnsDefinitions"
+            v-for="(col, index) in columnsDefinitions"
+            :key="index"
             class="cycle-editable-cell"
           >
             {{ item[col.property] }}
           </td>
 
           <td class="d-flex flex-row justify-center actions-cell">
-            <v-menu
-              min-width="120"
-              location="end"
-              class="action-menu"
-            >
-              <template v-slot:activator="{ props }">
+            <v-menu min-width="120" location="end" class="action-menu">
+              <template #activator="{ props: menuProps }">
                 <v-btn
-                  v-if="actions.includes(TABLE_ACTION.EDIT) || actions.includes(TABLE_ACTION.DELETE)"
-                  v-bind="props"
+                  v-if="
+                    actions.includes(TABLE_ACTION.EDIT) ||
+                    actions.includes(TABLE_ACTION.DELETE)
+                  "
+                  v-bind="menuProps"
                   :flat="true"
                   icon="fas fa-ellipsis-vertical"
                 />
@@ -52,18 +49,18 @@ A data table for the parameters page
                 >
                   <v-list-item-title>
                     <v-icon>fas fa-pen</v-icon>
-                    {{ $t("edit") }}
+                    {{ $t('edit') }}
                   </v-list-item-title>
                 </v-list-item>
 
                 <v-list-item
                   v-if="actions.includes(TABLE_ACTION.DELETE)"
-                  @click="emit('deleteClicked', item)"
                   class="theme-danger"
+                  @click="emit('deleteClicked', item)"
                 >
                   <v-list-item-title icon="fas fa-trash">
                     <v-icon>fas fa-trash</v-icon>
-                    {{ $t("delete") }}
+                    {{ $t('delete') }}
                   </v-list-item-title>
                 </v-list-item>
               </v-list>
@@ -76,12 +73,15 @@ A data table for the parameters page
           <td>
             <i>{{ i18n.t('nothing_to_show') }}</i>
           </td>
-          <td></td>
+          <td />
         </tr>
       </tbody>
     </v-table>
 
-    <div v-if="actions.includes(TABLE_ACTION.ADD)" class="d-flex justify-center my-3">
+    <div
+      v-if="actions.includes(TABLE_ACTION.ADD)"
+      class="d-flex justify-center my-3"
+    >
       <v-btn
         prepend-icon="fa fa-plus"
         class="theme-neutral"
@@ -94,9 +94,9 @@ A data table for the parameters page
 </template>
 
 <script setup lang="ts">
-import {TABLE_ACTION} from '~/types/enum/enums';
-import type {ColumnDefinition} from '~/types/interfaces';
-import {useDisplay} from 'vuetify';
+import { useDisplay } from 'vuetify'
+import { TABLE_ACTION } from '~/types/enum/enums'
+import type { ColumnDefinition } from '~/types/interfaces'
 
 const props = defineProps({
   /**
@@ -110,7 +110,7 @@ const props = defineProps({
   title: {
     type: String,
     required: false,
-    default: null
+    default: null,
   },
   /**
    * If provided, define the columns to show.
@@ -124,7 +124,7 @@ const props = defineProps({
   columnsDefinitions: {
     type: Array as PropType<Array<ColumnDefinition> | null>,
     required: false,
-    default: null
+    default: null,
   },
   /**
    * The property used as identifier (required by 'edition' link)
@@ -132,7 +132,7 @@ const props = defineProps({
   identifier: {
     type: String,
     required: false,
-    default: 'id'
+    default: 'id',
   },
   /**
    * List of the actions available for each record
@@ -140,7 +140,7 @@ const props = defineProps({
   actions: {
     type: Array as PropType<Array<TABLE_ACTION>>,
     required: false,
-    default: [TABLE_ACTION.EDIT, TABLE_ACTION.DELETE, TABLE_ACTION.ADD]
+    default: () => [TABLE_ACTION.EDIT, TABLE_ACTION.DELETE, TABLE_ACTION.ADD],
   },
   /**
    * The URL for the edit / create pages
@@ -152,8 +152,8 @@ const props = defineProps({
   actionsRoute: {
     type: String,
     required: false,
-    default: '/parameters'
-  }
+    default: '/parameters',
+  },
 })
 
 const i18n = useI18n()
@@ -167,18 +167,16 @@ const getId = (item: object) => {
 }
 
 const columns: ComputedRef<Array<ColumnDefinition>> = computed(() => {
-  return props.columnsDefinitions.map(col => {
+  return props.columnsDefinitions.map((col) => {
     return {
       property: col.property,
-      label: col.label ?? i18n.t(col.property)
+      label: col.label ?? i18n.t(col.property),
     }
   })
 })
-
 </script>
 
 <style scoped lang="scss">
-
 .container {
   //max-width: 1000px;
   //margin: 0 auto;
@@ -192,7 +190,8 @@ const columns: ComputedRef<Array<ColumnDefinition>> = computed(() => {
     font-weight: 600;
 
     td {
-      border-bottom: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
+      border-bottom: thin solid
+        rgba(var(--v-border-color), var(--v-border-opacity));
     }
 
     td:last-of-type {
@@ -207,7 +206,8 @@ const columns: ComputedRef<Array<ColumnDefinition>> = computed(() => {
     }
   }
 
-  th, td {
+  th,
+  td {
     padding: 10px;
     text-align: left;
   }

+ 12 - 19
components/Layout/Parameters/Website/ActivationSwitch.vue

@@ -9,7 +9,7 @@
       false-icon="fas fa-xmark"
       true-icon="fas fa-check"
       hide-details
-      @update:modelValue="onUpdate"
+      @update:model-value="onUpdate"
     />
 
     <LazyLayoutDialog :show="showWebsiteDeactivationDialog" theme="warning">
@@ -20,19 +20,14 @@
         <v-col>
           <div>
             {{
-              $t(
-                'yourOpentalentWebsiteWillBeDeactivatedOnceYouLlHaveSaved',
-              )
+              $t('yourOpentalentWebsiteWillBeDeactivatedOnceYouLlHaveSaved')
             }}.
           </div>
           <span>{{ $t('doYouWantToContinue') }} ?</span>
         </v-col>
       </template>
       <template #dialogBtn>
-        <v-btn
-          class="theme-neutral-soft mr-4"
-          @click="onDialogNoBtnClick"
-        >
+        <v-btn class="theme-neutral-soft mr-4" @click="onDialogNoBtnClick">
           {{ $t('cancel') }}
         </v-btn>
 
@@ -45,9 +40,8 @@
 </template>
 
 <script setup lang="ts">
-
-import {useTheme} from 'vuetify';
-import type {Ref} from 'vue';
+import { useTheme } from 'vuetify'
+import type { Ref } from 'vue'
 
 const theme = useTheme()
 
@@ -59,12 +53,13 @@ const props = defineProps({
   modelValue: {
     type: Boolean,
     required: true,
-  }
+  },
 })
 
-const color = computed(() => props.modelValue ?
-  theme.current.value.colors['success'] :
-  theme.current.value.colors['danger']
+const color = computed(() =>
+  props.modelValue
+    ? theme.current.value.colors.success
+    : theme.current.value.colors.danger,
 )
 
 const showWebsiteDeactivationDialog: Ref<boolean> = ref(false)
@@ -84,10 +79,8 @@ const onDialogYesBtnClick = () => {
 
 const onDialogNoBtnClick = () => {
   showWebsiteDeactivationDialog.value = false
-  props.modelValue = true
+  emit('update:modelValue', true)
 }
 </script>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 4 - 4
components/Layout/SubHeader/ActivityYear.vue

@@ -29,12 +29,12 @@
 </template>
 
 <script setup lang="ts">
+import { useDisplay } from 'vuetify'
 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'
 
@@ -63,16 +63,16 @@ const label: string = organizationProfileStore.isSchool
 const setActivityYear = async (event: string) => {
   const activityYear = parseInt(event)
 
-  if (!(1900 < activityYear) || !(activityYear <= 2100)) {
+  if (!(activityYear > 1900) || !(activityYear <= 2100)) {
     throw new Error("Error: 'year' shall be a valid year")
   }
   formStore.setDirty(false)
 
   pageStore.loading = true
   await em.patch(Access, accessProfileStore.currentAccessId, {
-    activityYear: activityYear,
+    activityYear,
   })
-  if (process.server) {
+  if (import.meta.server) {
     // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
     await refreshProfile()
   }

+ 5 - 8
components/Layout/SubHeader/Breadcrumbs.vue

@@ -3,10 +3,10 @@
 </template>
 
 <script setup lang="ts">
-import { computed } from '@vue/reactivity'
-import type { ComputedRef } from '@vue/reactivity'
-import type { AnyJson } from '~/types/data'
+import { computed } from 'vue'
+import type { ComputedRef } from 'vue'
 import { useI18n } from 'vue-i18n'
+import type { AnyJson } from '~/types/data'
 import UrlUtils from '~/services/utils/urlUtils'
 
 const runtimeConfig = useRuntimeConfig()
@@ -16,8 +16,7 @@ const router = useRouter()
 const items: ComputedRef<Array<AnyJson>> = computed(() => {
   const crumbs: Array<AnyJson> = []
   const baseUrl =
-    runtimeConfig.baseUrlAdminLegacy ??
-    runtimeConfig.public.baseUrlAdminLegacy
+    runtimeConfig.baseUrlAdminLegacy ?? runtimeConfig.public.baseUrlAdminLegacy
 
   crumbs.push({
     title: i18n.t('welcome'),
@@ -31,9 +30,7 @@ const items: ComputedRef<Array<AnyJson>> = computed(() => {
   pathPart.forEach((part) => {
     path = UrlUtils.join(path, part)
 
-    let match
-
-    match = router.resolve(path)
+    const match = router.resolve(path)
     if (match.name) {
       crumbs.push({
         title: !parseInt(part, 10)

+ 7 - 6
components/Layout/SubHeader/DataTiming.vue

@@ -14,10 +14,11 @@
       border
       :rounded="true"
       class="toggle-btn"
-      @update:modelValue="onUpdate"
+      @update:model-value="onUpdate"
     >
       <v-btn
-        v-for="choice in historicalChoices"
+        v-for="(choice, index) in historicalChoices"
+        :key="index"
         :value="choice"
         max-height="25"
         :class="
@@ -36,17 +37,17 @@
 </template>
 
 <script setup lang="ts">
+import type { Ref } from 'vue'
+import { useDisplay, useTheme } from 'vuetify'
 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
-const color = useTheme().current.value.colors['primary']
+const color = useTheme().current.value.colors.primary
 
 const { setDirty } = useFormStore()
 const accessProfileStore = useAccessProfileStore()
@@ -84,7 +85,7 @@ const onUpdate = async (newValue: Array<string>) => {
   await em.patch(Access, accessId, {
     historical: accessProfileStore.historical,
   })
-  if (process.server) {
+  if (import.meta.server) {
     // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
     await refreshProfile()
   }

+ 4 - 4
components/Layout/SubHeader/DataTimingRange.vue

@@ -9,7 +9,7 @@
         :model-value="datesRange"
         :max-height="28"
         :range="true"
-        :autoApply="false"
+        :auto-apply="false"
         @update:model-value="updateDateTimeRange"
       />
     </div>
@@ -17,7 +17,7 @@
 </template>
 
 <script setup lang="ts">
-import type { Ref } from '@vue/reactivity'
+import type { Ref } from 'vue'
 import { useAccessProfileStore } from '~/stores/accessProfile'
 import { useFormStore } from '~/stores/form'
 import { useEntityManager } from '~/composables/data/useEntityManager'
@@ -39,7 +39,7 @@ 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<void> => {
   const accessId = accessProfileStore.currentAccessId
 
   datesRange.value = dates
@@ -62,7 +62,7 @@ const updateDateTimeRange = async (dates: Array<Date>): Promise<any> => {
   await em.patch(Access, accessId, {
     historical: accessProfileStore.historical,
   })
-  if (process.server) {
+  if (import.meta.server) {
     // Force profile refresh server side to avoid a bug where server and client stores diverge on profile refresh
     await refreshProfile()
   }

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

@@ -1,6 +1,6 @@
 <template>
   <main>
-    <a ref="btn" id="activator">
+    <a id="activator" ref="btn">
       {{ $t('my_list') }}
     </a>
 
@@ -51,12 +51,12 @@
 </template>
 
 <script setup lang="ts">
+import { ref } from 'vue'
+import type { ComputedRef, Ref } from 'vue'
 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 type ApiResource from '~/models/ApiResource'
 import UrlUtils from '~/services/utils/urlUtils'
 
 const btn: Ref = ref(null)
@@ -84,8 +84,8 @@ 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
+      item.label.toLowerCase().includes(search.value.toLowerCase()) ||
+      item.menuKey.toLowerCase().includes(search.value.toLowerCase())
     )
   })
 })

+ 4 - 4
components/Layout/Subheader.vue

@@ -68,11 +68,11 @@ Contient entre autres le breadcrumb, les commandes de changement d'année et les
 </template>
 
 <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 { computed, ref } from 'vue'
+import type { ComputedRef, Ref } from 'vue'
 import { useDisplay } from 'vuetify'
+import { useMenu } from '~/composables/layout/useMenu'
+import { useAccessProfileStore } from '~/stores/accessProfile'
 
 const { smAndUp, lgAndUp } = useDisplay()
 const accessProfile = useAccessProfileStore()

+ 2 - 2
components/Layout/UpgradePremiumButton.vue

@@ -13,15 +13,15 @@
 
     <LayoutDialogTrialAlreadyDid
       :show="showDialog"
-      @closeDialog="showDialog = false"
+      @close-dialog="showDialog = false"
     />
   </div>
 </template>
 
 <script setup lang="ts">
+import { computed } from 'vue'
 import UrlUtils from '~/services/utils/urlUtils'
 import { useApiLegacyRequestService } from '~/composables/data/useApiLegacyRequestService'
-import { computed } from '@vue/reactivity'
 
 const runtimeConfig = useRuntimeConfig()
 const organizationProfile = useOrganizationProfileStore()

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

@@ -17,7 +17,7 @@ Bouton Delete avec modale de confirmation de la suppression
 
 <script setup lang="ts">
 import type { Ref, PropType } from 'vue'
-import ApiResource from '~/models/ApiResource'
+import type ApiResource from '~/models/ApiResource'
 import { useDeleteItem } from '~/composables/form/useDeleteItem'
 
 const props = defineProps({

+ 16 - 15
components/Ui/Button/Submit.vue

@@ -1,24 +1,24 @@
 <template>
   <v-btn
+    ref="mainBtn"
     class="submit-btn theme-primary"
     :class="hasOtherActions ? 'pr-0' : ''"
-    @click="submitAction(mainAction)"
-    ref="mainBtn"
     :disabled="validationPending"
+    @click="submitAction(mainAction)"
   >
     {{ $t(mainAction) }}
 
-    <v-divider class="ml-3" :vertical="true" v-if="hasOtherActions"></v-divider>
+    <v-divider v-if="hasOtherActions" class="ml-3" :vertical="true" />
 
     <v-menu
+      v-if="hasOtherActions"
       :top="dropDirection === 'top'"
       offset-y
       left
-      v-if="hasOtherActions"
       :nudge-top="dropDirection === 'top' ? 6 : 0"
       :nudge-bottom="dropDirection === 'bottom' ? 6 : 0"
     >
-      <template #activator="{ on, attrs }">
+      <template #activator="{ on }">
         <v-toolbar-title v-on="on">
           <v-icon class="pl-3 pr-3">
             {{
@@ -29,16 +29,14 @@
       </template>
       <v-list :min-width="menuSize">
         <v-list-item
-          dense
-          v-for="(action, index) in actions"
+          v-for="(action, index) in otherActions"
           :key="index"
+          dense
           class="subAction"
-          v-if="index > 0"
         >
-          <v-list-item-title
-            v-text="$t(action)"
-            @click="submitAction(action)"
-          />
+          <v-list-item-title @click="submitAction(action)">
+            {{ $t(action) }}
+          </v-list-item-title>
         </v-list-item>
       </v-list>
     </v-menu>
@@ -46,8 +44,8 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from '@vue/reactivity'
-import type { ComputedRef, Ref } from '@vue/reactivity'
+import { computed, ref } from 'vue'
+import type { ComputedRef, Ref } from 'vue'
 
 const props = defineProps({
   actions: {
@@ -85,10 +83,13 @@ const mainAction: ComputedRef<string> = computed(() => {
 const hasOtherActions: ComputedRef<boolean> = computed(() => {
   return props.actions.length > 1
 })
+
+const otherActions: ComputedRef<Array<string>> = computed(() => {
+  return props.actions.filter((_, index) => index > 0) as Array<string>
+})
 </script>
 
 <style scoped>
-
 .submit-btn {
   margin-right: 12px;
 

+ 5 - 3
components/Ui/Collection.vue

@@ -5,7 +5,7 @@
     <v-skeleton-loader v-if="pending" :type="loaderType" />
     <div v-else>
       <!-- Content -->
-      <slot name="list.item" v-bind="{ collection.items }" />
+      <slot name="list.item" v-bind="{ items: collection?.items }" />
 
       <!-- New button -->
       <v-btn v-if="newLink" class="theme-primary float-right">
@@ -20,8 +20,8 @@
 </template>
 
 <script setup lang="ts">
-import { computed, toRefs } from '@vue/reactivity'
-import type { ComputedRef, ToRefs } from '@vue/reactivity'
+import { computed, toRefs } from 'vue'
+import type { ComputedRef, ToRefs } from 'vue'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import type { Collection } from '~/types/data'
 
@@ -33,6 +33,7 @@ const props = defineProps({
   parent: {
     type: Object,
     required: false,
+    default: () => null,
   },
   loaderType: {
     type: String,
@@ -42,6 +43,7 @@ const props = defineProps({
   newLink: {
     type: String,
     required: false,
+    default: null,
   },
 })
 

+ 17 - 7
components/Ui/DataTable.vue

@@ -13,9 +13,13 @@ Tableau interactif conçu pour l'affichage d'une collection d'entités
       :loading="$fetchState.pending"
       class="elevation-1"
     >
-      <template v-for="header in headersWithItem" #[header.item]="props">
-        <slot :name="header.item" v-bind="props">
-          {{ props.item[header.value] }}
+      <template
+        v-for="(header, index) in headersWithItem"
+        :key="index"
+        #[header.item]="slotProps"
+      >
+        <slot :name="header.item" v-bind="slotProps">
+          {{ slotProps.item[header.value] }}
         </slot>
       </template>
 
@@ -28,10 +32,10 @@ Tableau interactif conçu pour l'affichage d'une collection d'entités
 </template>
 
 <script setup lang="ts">
-import { ref, toRefs } from '@vue/reactivity'
-import type { Ref } from '@vue/reactivity'
+import { ref, toRefs } from 'vue'
+import type { Ref } from 'vue'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
-import ApiResource from '~/models/ApiResource'
+import type ApiResource from '~/models/ApiResource'
 import type { AnyJson } from '~/types/data'
 
 const props = defineProps({
@@ -51,8 +55,14 @@ const props = defineProps({
 
 const { parent, model, headers } = toRefs(props)
 
+interface TableHeader {
+  value: string
+  item?: string
+  [key: string]: string | undefined
+}
+
 const headersWithItem = computed(() => {
-  return headers.value.map((header: any) => {
+  return headers.value.map((header: TableHeader) => {
     header.item = 'item.' + header.value
     return header
   })

+ 32 - 29
components/Ui/DatePicker.vue

@@ -7,35 +7,38 @@ Sélecteur de dates
 <template>
   <div class="date-picker">
     <VueDatePicker
-        :model-value="modelValue"
-        :locale="locale"
-        :format="dateFormatPattern"
-        :format-locale="fnsLocale"
-        :range="range"
-        :multi-calendars="range"
-        :enable-time-picker="withTimePicker"
-        :auto-apply="autoApply"
-        :auto-position="true"
-        :start-date="today"
-        close-on-scroll
-        text-input
-        :readonly="readonly"
-        position="left"
-        :teleport="true"
-        :select-text="$t('select')"
-        :cancel-text="$t('cancel')"
-        input-class-name="date-picker-input"
-        @update:model-value="$emit('update:modelValue', $event)"
-        class="mb-6"
-      />
+      :model-value="modelValue"
+      :locale="locale"
+      :format="dateFormatPattern"
+      :format-locale="fnsLocale"
+      :range="range"
+      :multi-calendars="range"
+      :enable-time-picker="withTimePicker"
+      :auto-apply="autoApply"
+      :auto-position="true"
+      :start-date="today"
+      close-on-scroll
+      text-input
+      :readonly="readonly"
+      position="left"
+      :teleport="true"
+      :select-text="$t('select')"
+      :cancel-text="$t('cancel')"
+      input-class-name="date-picker-input"
+      class="mb-6"
+      @update:model-value="emit('update:modelValue', $event)"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
-import DateUtils, { supportedLocales } from '~/services/utils/dateUtils'
-import type { PropType } from '@vue/runtime-core';
-import type { Locale } from 'date-fns';
+import type { PropType } from 'vue'
+import type { Locale } from 'date-fns'
+import type { supportedLocales } from '~/services/utils/dateUtils'
+import DateUtils from '~/services/utils/dateUtils'
+
+const emit = defineEmits(['update:modelValue'])
 
 const props = defineProps({
   modelValue: {
@@ -58,19 +61,19 @@ const props = defineProps({
   readonly: {
     type: Boolean,
     default: false,
-  }
+  },
 })
 
 const i18n = useI18n()
 
 const locale: Ref<string> = i18n.locale
 
-const fnsLocale: ComputedRef<Locale> = computed(
-  () => DateUtils.getFnsLocale(locale.value as supportedLocales)
+const fnsLocale: ComputedRef<Locale> = computed(() =>
+  DateUtils.getFnsLocale(locale.value as supportedLocales),
 )
 
-const dateFormatPattern: ComputedRef<string> = computed(
-  () => DateUtils.getShortFormatPattern(locale.value as supportedLocales)
+const dateFormatPattern: ComputedRef<string> = computed(() =>
+  DateUtils.getShortFormatPattern(locale.value as supportedLocales),
 )
 
 const today = new Date()

+ 20 - 14
components/Ui/Form.vue

@@ -30,7 +30,7 @@ de quitter si des données ont été modifiées.
               :actions="actions"
               :validation-pending="validationPending || !isValid"
               @submit="submit"
-            ></UiButtonSubmit>
+            />
           </v-col>
         </v-row>
       </v-container>
@@ -53,7 +53,7 @@ de quitter si des données ont été modifiées.
               :validation-pending="validationPending || !isValid"
               :actions="actions"
               @submit="submit"
-            ></UiButtonSubmit>
+            />
           </v-col>
         </v-row>
       </v-container>
@@ -98,7 +98,7 @@ import * as _ from 'lodash-es'
 import { FORM_FUNCTION, SUBMIT_TYPE, TYPE_ALERT } from '~/types/enum/enums'
 import { useFormStore } from '~/stores/form'
 import { useEntityManager } from '~/composables/data/useEntityManager'
-import ApiModel from '~/models/ApiModel'
+import type ApiModel from '~/models/ApiModel'
 import { usePageStore } from '~/stores/page'
 import type { AnyJson } from '~/types/data'
 import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
@@ -145,9 +145,9 @@ const props = defineProps({
     default: false,
   },
   /**
-   * Faut-il rafraichir le profil à la soumission du formulaire?
+   * Faut-il rafraichir le profil à la soumission du formulaire ?
    */
-  refreshProfile: {
+  refreshProfileOnSubmit: {
     type: Boolean,
     required: false,
     default: false,
@@ -230,7 +230,7 @@ const submit = async (next: string | null = null) => {
 
     emit('update:model-value', updatedEntity)
 
-    if (props.refreshProfile) {
+    if (props.refreshProfileOnSubmit) {
       await refreshProfile()
     }
 
@@ -246,17 +246,23 @@ const submit = async (next: string | null = null) => {
     } else if (next === SUBMIT_TYPE.SAVE_AND_BACK) {
       onSaveAndQuitAction(actionArgs)
     }
-  } catch (error: any) {
+  } catch (error: unknown) {
+    const err = error as {
+      response?: {
+        status: number
+        data: { violations?: Array<{ message: string; propertyPath: string }> }
+      }
+    }
     if (
-      error.response &&
-      error.response.status === 422 &&
-      error.response.data.violations
+      err.response &&
+      err.response.status === 422 &&
+      err.response.data.violations
     ) {
       // TODO: à revoir
       const violations: Array<string> = []
       let fields: AnyJson = {}
 
-      for (const violation of error.response.data.violations) {
+      for (const violation of err.response.data.violations) {
         violations.push(i18n.t(violation.message) as string)
         fields = Object.assign(fields, {
           [violation.propertyPath]: violation.message,
@@ -390,11 +396,12 @@ watch(props.modelValue, async (newEntity, oldEntity) => {
  * @param e
  */
 // TODO: voir si encore nécessaire avec le @submit.prevent
-const preventSubmit = (e: any) => {
+const preventSubmit = (e: Event) => {
   // Cancel the event
   e.preventDefault()
   // Chrome requires returnValue to be set
-  e.returnValue = ''
+  const event = e as { returnValue: string }
+  event.returnValue = ''
 }
 
 /**
@@ -425,7 +432,6 @@ defineExpose({ validate })
       }
     }
   }
-
 }
 
 .confirmation-dlg-actions {

+ 7 - 7
components/Ui/Form/Creation.vue

@@ -1,5 +1,5 @@
 <template>
-  <UiForm v-model="entity" :submitActions="submitActions">
+  <UiForm v-model="entity" :submit-actions="submitActions">
     <template #form.button>
       <v-btn v-if="goBackRoute" class="theme-neutral mr-3" @click="quit">
         {{ $t('cancel') }}
@@ -11,9 +11,9 @@
 </template>
 
 <script setup lang="ts">
-import type { PropType } from '@vue/runtime-core'
+import type { PropType } from 'vue'
 import type { RouteLocationRaw } from '@intlify/vue-router-bridge'
-import ApiModel from '~/models/ApiModel'
+import type ApiModel from '~/models/ApiModel'
 import type { AnyJson } from '~/types/data'
 import { SUBMIT_TYPE } from '~/types/enum/enums'
 import { useEntityManager } from '~/composables/data/useEntityManager'
@@ -23,7 +23,7 @@ const props = defineProps({
    * Classe de l'ApiModel (ex: Organization, Notification, ...)
    */
   model: {
-    type: Function as any as () => typeof ApiModel,
+    type: Function as PropType<() => typeof ApiModel>,
     required: true,
   },
   /**
@@ -55,11 +55,11 @@ const props = defineProps({
 const router = useRouter()
 const { em } = useEntityManager()
 
-//@ts-ignore Pour une raison que j'ignore, le type Ref<ApiModel> met en erreur la prop entity de UiForm...
+// @ts-expect-error Pour une raison que j'ignore, le type Ref<ApiModel> met en erreur la prop entity de UiForm...
 const entity: ApiModel = reactive(em.newInstance(props.model))
 
 const submitActions = computed(() => {
-  let actions: AnyJson = {}
+  const actions: AnyJson = {}
 
   if (props.goBackRoute !== null) {
     actions[SUBMIT_TYPE.SAVE_AND_BACK] = props.goBackRoute
@@ -71,7 +71,7 @@ const submitActions = computed(() => {
 
 const quit = () => {
   if (!props.goBackRoute) {
-    throw Error('no go back route defined')
+    throw new Error('no go back route defined')
   }
 
   router.push(props.goBackRoute)

+ 9 - 11
components/Ui/Form/DeletionConfirmationDialog.vue

@@ -1,9 +1,5 @@
 <template>
-  <LazyLayoutDialog
-    :show="modelValue"
-    theme="danger"
-  >
-
+  <LazyLayoutDialog :show="modelValue" theme="danger">
     <template #dialogTitle>
       {{ $t('caution') }}
     </template>
@@ -28,11 +24,15 @@
 <script setup lang="ts">
 const props = defineProps({
   modelValue: {
-    type: Boolean
-  }
+    type: Boolean,
+  },
 })
 
-const emit = defineEmits(['cancelClicked', 'deleteClicked', 'update:modelValue'])
+const emit = defineEmits([
+  'cancelClicked',
+  'deleteClicked',
+  'update:modelValue',
+])
 
 const onCancelClicked = () => {
   emit('cancelClicked')
@@ -45,6 +45,4 @@ const onDeleteClicked = () => {
 }
 </script>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 9 - 9
components/Ui/Form/Edition.vue

@@ -1,7 +1,7 @@
 <template>
   <LayoutContainer>
     <UiLoadingPanel v-if="pending" />
-    <UiForm v-else v-model="entity" :submitActions="submitActions">
+    <UiForm v-else v-model="entity" :submit-actions="submitActions">
       <template #form.button>
         <v-btn v-if="goBackRoute" class="theme-neutral mr-3" @click="quit">
           {{ $t('cancel') }}
@@ -14,21 +14,21 @@
 </template>
 
 <script setup lang="ts">
-import type { PropType } from '@vue/runtime-core'
-import ApiModel from '~/models/ApiModel'
-import type { AnyJson } from '~/types/data'
-import { SUBMIT_TYPE } from '~/types/enum/enums'
+import type { PropType } from 'vue'
 import { useRoute } from 'vue-router'
 import type { RouteLocationRaw } from 'vue-router'
-import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import type { AsyncData } from '#app'
+import type ApiModel from '~/models/ApiModel'
+import type { AnyJson } from '~/types/data'
+import { SUBMIT_TYPE } from '~/types/enum/enums'
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
 
 const props = defineProps({
   /**
    * Classe de l'ApiModel (ex: Organization, Notification, ...)
    */
   model: {
-    type: Function as any as () => typeof ApiModel,
+    type: Function as PropType<() => typeof ApiModel>,
     required: true,
   },
   /**
@@ -79,7 +79,7 @@ const { data: entity, pending } = fetch(props.model, entityId) as AsyncData<
 >
 
 const submitActions = computed(() => {
-  let actions: AnyJson = {}
+  const actions: AnyJson = {}
 
   if (props.goBackRoute !== null) {
     actions[SUBMIT_TYPE.SAVE_AND_BACK] = props.goBackRoute
@@ -91,7 +91,7 @@ const submitActions = computed(() => {
 
 const quit = () => {
   if (!props.goBackRoute) {
-    throw Error('no go back route defined')
+    throw new Error('no go back route defined')
   }
 
   router.push(props.goBackRoute)

+ 10 - 10
components/Ui/Help.vue

@@ -12,24 +12,24 @@
   >
     <template #activator="{}">
       <v-icon
-        @click="onIconClicked"
+        ref="iconRef"
         icon
         class="ml-3"
         size="18px"
-        ref="iconRef"
+        @click="onIconClicked"
       >
         {{ icon }}
       </v-icon>
     </template>
 
-    <div ref="slotDiv" class="tooltip" v-click-out="onClickOutside">
-      <slot></slot>
+    <div ref="slotDiv" v-click-out="onClickOutside" class="tooltip">
+      <slot />
     </div>
   </v-tooltip>
 </template>
 
 <script setup lang="ts">
-import type { Ref } from '@vue/reactivity'
+import type { Ref } from 'vue'
 
 const props = defineProps({
   left: {
@@ -61,24 +61,24 @@ const props = defineProps({
 
 const { $refs } = useNuxtApp()
 
-const show: Ref<Boolean> = ref(false)
+const show: Ref<boolean> = ref(false)
 
 // Template reference to the icon object
 const iconRef = ref(null)
 
 // Left is the default, set it to true if not any other is true
-const leftOrDefault: Ref<Boolean> = ref(
+const leftOrDefault: Ref<boolean> = ref(
   props.left || (!props.right && !props.bottom && !props.top),
 )
 
-const onIconClicked = (e: any) => {
+const onIconClicked = (e: MouseEvent) => {
   show.value = !show.value
   e.stopPropagation()
 }
 
-const onClickOutside = (e: any) => {
+const onClickOutside = (e: MouseEvent) => {
   if (show.value) {
-    if (e.target === (iconRef.value as any).$el) {
+    if (e.target === (iconRef.value as { $el: HTMLElement }).$el) {
       return
     }
     show.value = false

+ 27 - 12
components/Ui/Image.vue

@@ -14,10 +14,10 @@ Permet d'afficher une image par défaut si l'image demandée n'est pas disponibl
       >
         <template #placeholder>
           <v-row
+            v-if="pending"
             class="fill-height ma-0"
             align="center"
             justify="center"
-            v-if="pending"
           >
             <v-progress-circular :indeterminate="true" color="neutral" />
           </v-row>
@@ -36,10 +36,10 @@ Permet d'afficher une image par défaut si l'image demandée n'est pas disponibl
 </template>
 
 <script setup lang="ts">
+import type { WatchStopHandle } from 'vue'
 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 { IMAGE_SIZE } from '~/types/enum/enums'
 
 const props = defineProps({
   /**
@@ -56,6 +56,7 @@ const props = defineProps({
   defaultImage: {
     type: String,
     required: false,
+    default: null,
   },
   /**
    * Hauteur de l'image à l'écran (en px)
@@ -63,6 +64,7 @@ const props = defineProps({
   height: {
     type: Number,
     required: false,
+    default: null,
   },
   /**
    * Largeur de l'image à l'écran (en px)
@@ -70,6 +72,15 @@ const props = defineProps({
   width: {
     type: Number,
     required: false,
+    default: null,
+  },
+  /**
+   * Taille de l'image fetchée depuis l'API (prédimensionnement)
+   */
+  size: {
+    type: String as PropType<IMAGE_SIZE>,
+    required: false,
+    default: IMAGE_SIZE.MD,
   },
   /**
    * Icône à afficher en overlay au survol de la souris
@@ -89,25 +100,29 @@ const emit = defineEmits(['overlay-clicked'])
 
 const fileId = toRef(props, 'imageId')
 
-const {
-  data: imageSrc,
-  pending,
-  refresh: refreshImage,
-} = (await fetch(fileId, defaultImagePath, props.height, props.width)) as any
-
-console.log(imageSrc.value)
-
 const refresh = () => {
   refreshImage()
 }
 defineExpose({ refresh })
 
+interface FetchResult {
+  data: Ref<string | null>
+  pending: Ref<boolean>
+  refresh: () => void
+}
+
+const {
+  data: imageSrc,
+  pending,
+  refresh: refreshImage,
+} = (await fetch(fileId, props.size, defaultImagePath)) as FetchResult
+
 /**
  * Si l'id change, on recharge l'image
  */
 const unwatch: WatchStopHandle = watch(
   () => props.imageId,
-  async (value, oldValue) => {
+  async () => {
     refresh()
   },
 )

+ 15 - 13
components/Ui/Input/Autocomplete.vue

@@ -8,6 +8,7 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
   <main>
     <!--suppress TypeScriptValidateTypes -->
     <v-autocomplete
+      v-model:search-input="search"
       :model-value="modelValue"
       autocomplete="search"
       :items="items"
@@ -19,7 +20,6 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
       :multiple="multiple"
       :loading="isLoading"
       :return-object="returnObject"
-      :search-input.sync="search"
       :prepend-inner-icon="prependInnerIcon"
       :error="error || !!fieldViolations"
       :error-messages="
@@ -34,33 +34,34 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
       "
       :variant="variant"
       density="compact"
+      class="mb-3"
       @update:model-value="onUpdate"
       @update:search="emit('update:search', $event)"
       @update:menu="emit('update:menu', $event)"
       @update:focused="emit('update:focused', $event)"
-      class="mb-3"
     >
-      <template v-if="slotText" #item="data">
-        <!--        <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>-->
+      <template v-if="slotText" #item="{ item }">
+        <div>{{ item.slotTextDisplay }}</div>
       </template>
     </v-autocomplete>
   </main>
 </template>
 
 <script setup lang="ts">
-import { computed } from '@vue/reactivity'
-import type { ComputedRef, Ref } from '@vue/reactivity'
+import { computed } from 'vue'
+import type { ComputedRef, Ref, PropType } from 'vue'
 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({
   /**
    * v-model
    */
   modelValue: {
-    type: [String, Number, Object, Array] as PropType<any>,
+    type: [String, Number, Object, Array] as PropType<
+      string | number | object | Array<unknown>
+    >,
     required: false,
     default: null,
   },
@@ -88,7 +89,7 @@ const props = defineProps({
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-items
    */
   items: {
-    type: Array as PropType<Array<Object>>,
+    type: Array as PropType<Array<object>>,
     required: false,
     default: () => [],
   },
@@ -138,6 +139,7 @@ const props = defineProps({
    */
   prependInnerIcon: {
     type: String,
+    default: null,
   },
   /**
    * Rends les résultats sous forme de puces
@@ -272,7 +274,7 @@ const onUpdate = (event: string) => {
  * TODO: à revoir
  */
 const items: ComputedRef<Array<AnyJson>> = computed(() => {
-  let _items: Array<any> = props.items
+  const _items: Array<AnyJson> = props.items
   return _items
   // if (props.group !== null) {
   //   _items = groupItems(props.items)
@@ -287,7 +289,7 @@ const items: ComputedRef<Array<AnyJson>> = computed(() => {
  *
  * @param items
  */
-const groupItems = (items: Array<any>): Array<Array<string>> => {
+const groupItems = (items: Array<AnyJson>): Array<Array<string>> => {
   const group = props.group as string | null
   if (group === null) {
     return items
@@ -328,7 +330,7 @@ const prepareGroups = (groupedItems: Array<Array<string>>): Array<AnyJson> => {
 
     // On parcourt les items pour préparer les texts / slotTexts à afficher
     finalItems = finalItems.concat(
-      groupedItems[group].map((item: any) => {
+      groupedItems[group].map((item: AnyJson) => {
         return prepareItem(item)
       }),
     )
@@ -342,7 +344,7 @@ const prepareGroups = (groupedItems: Array<Array<string>>): Array<AnyJson> => {
  *
  * @param item
  */
-const prepareItem = (item: Object): AnyJson => {
+const prepareItem = (item: object): AnyJson => {
   const slotTextDisplay: Array<string> = []
   const itemTextDisplay: Array<string> = []
 

+ 5 - 6
components/Ui/Input/Autocomplete/Accesses.vue

@@ -12,7 +12,7 @@ Champs autocomplete dédié à la recherche des Accesses d'une structure
       :label="label"
       :items="items"
       item-value="id"
-      :isLoading="pending"
+      :is-loading="pending"
       :multiple="multiple"
       hide-no-data
       :chips="chips"
@@ -21,21 +21,20 @@ Champs autocomplete dédié à la recherche des Accesses d'une structure
       prepend-inner-icon="fas fa-magnifying-glass"
       :return-object="false"
       :variant="variant"
+      :class="pending || pageStore.loading ? 'hide-selection' : ''"
       @update:model-value="onUpdateModelValue"
       @update:search="onUpdateSearch"
-      :class="(pending || pageStore.loading) ? 'hide-selection' : ''"
     />
   </main>
 </template>
 
 <script setup lang="ts">
-import type { PropType } from '@vue/runtime-core'
-import type { ComputedRef, Ref } from '@vue/reactivity'
-import { computed } from '@vue/reactivity'
+import type { PropType, ComputedRef, Ref } from 'vue'
+import { computed } from 'vue'
+import * as _ from 'lodash-es'
 import type { AssociativeArray } from '~/types/data'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import Access from '~/models/Access/Access'
-import * as _ from 'lodash-es'
 import Query from '~/services/data/Query'
 import OrderBy from '~/services/data/Filters/OrderBy'
 import { ORDER_BY_DIRECTION, SEARCH_STRATEGY } from '~/types/enum/data'

+ 16 - 12
components/Ui/Input/AutocompleteWithAPI.vue

@@ -11,27 +11,26 @@ d'une api)
       :label="label"
       :data="remoteData ? remoteData : data"
       :items="items"
-      :isLoading="isLoading"
+      :is-loading="isLoading"
       :item-text="itemText"
-      :slotText="slotText"
+      :slot-text="slotText"
       :item-value="itemValue"
       :multiple="multiple"
       :chips="chips"
-      prependIcon="mdi-magnify"
+      prepend-icon="mdi-magnify"
       :return-object="returnObject"
-      @research="search"
       :no-filter="noFilter"
+      @research="search"
       @update="$emit('update', $event, field)"
     />
   </main>
 </template>
 
 <script setup lang="ts">
-import { ref, toRefs } from '@vue/reactivity'
-import type { Ref } from '@vue/reactivity'
-import UrlUtils from '~/services/utils/urlUtils'
+import { ref, toRefs, watch } from 'vue'
+import type { Ref } from 'vue'
 import { useFetch } from '#app'
-import { watch } from '@vue/runtime-core'
+import UrlUtils from '~/services/utils/urlUtils'
 
 const props = defineProps({
   label: {
@@ -45,7 +44,9 @@ const props = defineProps({
     default: null,
   },
   searchFunction: {
-    type: Function,
+    type: Function as PropType<
+      (research: string, field?: string) => Promise<Array<unknown>>
+    >,
     required: true,
   },
   data: {
@@ -78,6 +79,7 @@ const props = defineProps({
   slotText: {
     type: Array,
     required: false,
+    default: () => [],
   },
   returnObject: {
     type: Boolean,
@@ -97,6 +99,8 @@ const props = defineProps({
   },
 })
 
+const emit = defineEmits(['update'])
+
 const { data } = toRefs(props)
 const items = ref([])
 const remoteData: Ref<Array<string> | null> = ref(null)
@@ -105,7 +109,7 @@ const isLoading = ref(false)
 if (props.data) {
   items.value = props.multiple ? (data.value ?? []) : [data.value]
 } else if (props.remoteUri) {
-  const ids: Array<any> = []
+  const ids: Array<string | number> = []
 
   for (const uri of props.remoteUri) {
     ids.push(UrlUtils.extractIdFromUri(uri as string))
@@ -119,7 +123,7 @@ if (props.data) {
   useFetch(async () => {
     isLoading.value = true
 
-    const r: any = await $fetch(props.remoteUrl, options)
+    const r: { data: Array<string> } = await $fetch(props.remoteUrl, options)
 
     isLoading.value = false
     remoteData.value = r.data
@@ -129,7 +133,7 @@ if (props.data) {
 
 const search = async (research: string) => {
   isLoading.value = true
-  const func: Function = props.searchFunction
+  const func = props.searchFunction
   items.value = items.value.concat(await func(research, props.field))
   isLoading.value = false
 }

+ 7 - 10
components/Ui/Input/AutocompleteWithAp2i.vue

@@ -10,24 +10,23 @@ Liste déroulante avec autocompletion issue de Ap2i
       :field="field"
       :label="label"
       :items="items"
-      :isLoading="pending"
+      :is-loading="pending"
       item-title="title"
       item-value="id"
       :multiple="multiple"
       :chips="chips"
-      prependIcon="fas fa-magnifying-glass"
+      prepend-icon="fas fa-magnifying-glass"
       :return-object="false"
     />
   </main>
 </template>
 
 <script setup lang="ts">
-import { computed } from '@vue/reactivity'
-import type { ComputedRef, Ref } from '@vue/reactivity'
-import type { PropType } from '@vue/runtime-core'
+import { computed } from 'vue'
+import type { ComputedRef, Ref, PropType } from 'vue'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
-import ApiResource from '~/models/ApiResource'
-import ApiModel from '~/models/ApiModel'
+import type ApiResource from '~/models/ApiResource'
+import type ApiModel from '~/models/ApiModel'
 import type { AnyJson, AssociativeArray } from '~/types/data'
 
 const props = defineProps({
@@ -43,7 +42,7 @@ const props = defineProps({
    * Classe de l'ApiModel (ex: Organization, Notification, ...) qui sert de source à la liste
    */
   model: {
-    type: Function as any as () => typeof ApiModel,
+    type: Function as PropType<typeof ApiModel>,
     required: true,
   },
   /**
@@ -137,8 +136,6 @@ const { data: collection, pending } = await fetchCollection(
 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 []

+ 2 - 0
components/Ui/Input/AutocompleteWithEnum.vue

@@ -18,6 +18,8 @@ import { useEnumFetch } from '~/composables/data/useEnumFetch'
 import ArrayUtils from '~/services/utils/arrayUtils'
 import type { Enum } from '~/types/data'
 
+const emit = defineEmits(['update:model-value'])
+
 const props = defineProps({
   modelValue: {
     type: String as PropType<string | null>,

+ 0 - 2
components/Ui/Input/Checkbox.vue

@@ -94,7 +94,6 @@ const onUpdate = (event: boolean) => {
 </script>
 
 <style scoped lang="scss">
-
 .checkbox {
   margin-top: -4px;
   margin-bottom: 8px;
@@ -109,5 +108,4 @@ const onUpdate = (event: boolean) => {
 :deep(.v-label) {
   padding-left: 8px;
 }
-
 </style>

+ 1 - 0
components/Ui/Input/Combobox.vue

@@ -32,6 +32,7 @@ const props = defineProps({
   modelValue: {
     type: [String, Number],
     required: false,
+    default: null,
   },
   /**
    * Nom de la propriété d'une entité lorsque l'input concerne cette propriété

+ 4 - 6
components/Ui/Input/DatePicker.vue

@@ -101,7 +101,6 @@ const date: Ref<Date | undefined> = ref(
 )
 
 const onUpdate = (event: string) => {
-  console.log(event)
   updateViolationState(event)
   date.value = event ? new Date(event) : undefined
   emit('update:model-value', date.value ? formatISO(date.value) : undefined)
@@ -109,7 +108,6 @@ const onUpdate = (event: string) => {
 </script>
 
 <style scoped lang="scss">
-
 .container {
   position: relative;
 }
@@ -123,8 +121,10 @@ const onUpdate = (event: string) => {
   padding: 0 0.3rem;
   font-size: 0.8rem;
   z-index: 1;
-  transition: color 0.2s, font-size 0.2s, top 0.2s;
-
+  transition:
+    color 0.2s,
+    font-size 0.2s,
+    top 0.2s;
 }
 
 .date-picker:hover {
@@ -146,6 +146,4 @@ const onUpdate = (event: string) => {
     color: #333333;
   }
 }
-
-
 </style>

+ 2 - 2
components/Ui/Input/Email.vue

@@ -16,10 +16,10 @@ Champs de saisie de type Text dédié à la saisie d'emails
 
 <script setup lang="ts">
 import { useNuxtApp } from '#app'
+import type { PropType } from 'vue'
+import { useI18n } from 'vue-i18n'
 import { useFieldViolation } from '~/composables/form/useFieldViolation'
 import { useValidationUtils } from '~/composables/utils/useValidationUtils'
-import type { PropType } from '@vue/runtime-core'
-import { useI18n } from 'vue-i18n'
 
 const props = defineProps({
   label: {

+ 4 - 4
components/Ui/Input/Enum.vue

@@ -23,7 +23,7 @@ Liste déroulante dédiée à l'affichage d'objets Enum
         errorMessage || (fieldViolations ? $t(fieldViolations) : '')
       "
       density="compact"
-      @update:modelValue="
+      @update:model-value="
         updateViolationState($event)
         $emit('update:modelValue', $event)
       "
@@ -40,7 +40,7 @@ const props = defineProps({
    * v-model
    */
   modelValue: {
-    String,
+    type: String,
     required: false,
     default: null,
   },
@@ -104,7 +104,7 @@ const props = defineProps({
 })
 
 if (typeof props.enum === 'undefined') {
-  throw new Error("missing 'enum' property for input")
+  throw new TypeError("missing 'enum' property for input")
 }
 
 const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
@@ -114,7 +114,7 @@ const { data: items, pending } = fetch(props.enum)
 
 const emit = defineEmits(['update:modelValue', 'change'])
 
-const onModelUpdate = (event: any) => {
+const onModelUpdate = (event: string | null) => {
   updateViolationState(event)
   emit('change', event)
   emit('update:modelValue', event)

+ 38 - 15
components/Ui/Input/Image.vue

@@ -27,8 +27,7 @@ Assistant de création d'image
             align="center"
             justify="center"
           >
-            <v-progress-circular :indeterminate="true" color="neutral">
-            </v-progress-circular>
+            <v-progress-circular :indeterminate="true" color="neutral" />
           </v-row>
 
           <div v-else>
@@ -82,8 +81,8 @@ Assistant de création d'image
         </v-btn>
         <v-btn
           class="submitBtn theme-primary"
-          @click="save"
           :disabled="pending"
+          @click="save"
         >
           {{ $t('save') }}
         </v-btn>
@@ -95,15 +94,27 @@ Assistant de création d'image
 <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 { type Ref, ref } from 'vue'
 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 { FILE_VISIBILITY, IMAGE_SIZE, TYPE_ALERT } from '~/types/enum/enums'
 import { usePageStore } from '~/stores/page'
 import FileUtils from '~/services/utils/fileUtils'
 
+interface CropperChangeEvent {
+  coordinates: {
+    left: number
+    top: number
+    width: number
+    height: number
+  }
+}
+
+interface UploadResponse {
+  fileId: number
+}
+
 const props = defineProps({
   /**
    * Id de l'objet File, ou null
@@ -127,6 +138,7 @@ const props = defineProps({
   defaultImage: {
     type: String,
     required: false,
+    default: null,
   },
   /**
    * Hauteur de l'image à l'écran (en px)
@@ -134,6 +146,7 @@ const props = defineProps({
   height: {
     type: Number,
     required: false,
+    default: null,
   },
   /**
    * Largeur de l'image à l'écran (en px)
@@ -141,6 +154,7 @@ const props = defineProps({
   width: {
     type: Number,
     required: false,
+    default: null,
   },
   /**
    * Donne la possibilité de rogner les images
@@ -156,6 +170,7 @@ const props = defineProps({
   ownerId: {
     type: Number,
     required: false,
+    default: null,
   },
 })
 
@@ -168,9 +183,9 @@ const emit = defineEmits(['update:modelValue'])
 /**
  * Références à des composants
  */
-const fileInput: Ref<null | any> = ref(null)
-const cropper: Ref<any> = ref(null)
-const uiImage: Ref<any> = ref(null)
+const fileInput: Ref<HTMLInputElement | null> = ref(null)
+const cropper: Ref<typeof Cropper | null> = ref(null)
+const uiImage: Ref<{ $el: HTMLElement } | null> = ref(null)
 
 /**
  * L'objet File ou l'image sont en cours de chargement
@@ -227,7 +242,12 @@ const defaultPosition = () => {
 /**
  * @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: {
+    imageSize?: { width: number; height: number }
+    visibleArea?: { width: number; height: number }
+  } | null,
+): { width: number; height: number } | null => {
   if (!params) {
     return null
   }
@@ -257,7 +277,7 @@ const loadImage = async (fileId: number) => {
 
   currentImage.value.name = file.value.name
   currentImage.value.id = file.value.id
-  currentImage.value.src = (await imageManager.get(fileId)) as string
+  currentImage.value.src = await imageManager.get(fileId, IMAGE_SIZE.RAW)
 }
 
 /**
@@ -305,7 +325,7 @@ const reset = () => {
  * Upload une image depuis le poste client
  * @param event
  */
-const uploadImage = async (event: any) => {
+const uploadImage = async (event: Event) => {
   const { files } = event.target
 
   if (!files || !files[0]) {
@@ -339,7 +359,9 @@ const uploadImage = async (event: any) => {
  * Lorsque le cropper change de position / taille, on met à jour les coordonnées
  * @param newCoordinates
  */
-const onCropperChange = ({ coordinates: newCoordinates }: any) => {
+const onCropperChange = ({
+  coordinates: newCoordinates,
+}: CropperChangeEvent) => {
   cropperConfig.value = newCoordinates
 }
 
@@ -377,7 +399,7 @@ const saveNewImage = async (): Promise<number> => {
     currentImage.value.content,
     FILE_VISIBILITY.EVERYBODY,
     config,
-  )) as any
+  )) as UploadResponse
 
   return response.fileId
 }
@@ -414,13 +436,14 @@ const save = async () => {
   } else if (currentImage.value.id) {
     // L'image existante a été modifiée
     await saveExistingImage()
-    uiImage.value.refresh()
   } else {
     // On a reset l'image
     emit('update:modelValue', null)
   }
 
   showModal.value = false
+
+  uiImage.value.refresh()
   pageStore.loading = false
 }
 

+ 0 - 1
components/Ui/Input/Number.vue

@@ -118,7 +118,6 @@ const keepInRange = (val: number) => {
 const emit = defineEmits(['update:modelValue'])
 
 const onModelUpdate = (event: string) => {
-  // eslint-disable-next-line vue/no-mutating-props
   // props.modelValue = keepInRange(cast(event))
   // emitUpdate()
   emit('update:modelValue', keepInRange(cast(event)))

+ 14 - 5
components/Ui/Input/Phone.vue

@@ -9,11 +9,11 @@ Champs de saisie d'un numéro de téléphone
 <template>
   <client-only>
     <vue-tel-input-vuetify
+      v-model="myPhone"
       :error="error || !!violation"
       :error-messages="errorMessage || violation ? $t(violation) : ''"
       :field="field"
       :label="label"
-      v-model="myPhone"
       :readonly="readonly"
       clearable
       valid-characters-only
@@ -27,8 +27,8 @@ Champs de saisie d'un numéro de téléphone
 
 <script setup lang="ts">
 import { useNuxtApp } from '#app'
+import type { Ref } from 'vue'
 import { useFieldViolation } from '~/composables/form/useFieldViolation'
-import type { Ref } from '@vue/reactivity'
 
 const props = defineProps({
   label: {
@@ -61,7 +61,9 @@ const props = defineProps({
   },
 })
 
-const { emit, i18n } = useNuxtApp()
+const { emit } = useNuxtApp()
+
+const i18n = useI18n()
 
 const { violation, onChange } = useFieldViolation(props.field, emit)
 
@@ -71,12 +73,19 @@ const isValid: Ref<boolean> = ref(false)
 const onInit: Ref<boolean> = ref(true)
 
 const onInput = (
-  _: any,
+  _: unknown,
   {
     number,
     valid,
     countryChoice,
-  }: { number: any; valid: boolean; countryChoice: any },
+  }: {
+    number: {
+      national: string | number
+      international: string | number
+    }
+    valid: boolean
+    countryChoice: Record<string, unknown>
+  },
 ) => {
   isValid.value = valid
   nationalNumber.value = number.national

+ 6 - 4
components/Ui/Input/Text.vue

@@ -1,5 +1,5 @@
 <!--
-Champs de saisie de texte, à placer dans un composant `UiForm`
+Champs de saisie de texte, à placer dans un composant UiForm
 
 @see https://vuetifyjs.com/en/components/text-fields/
 -->
@@ -28,9 +28,11 @@ Champs de saisie de texte, à placer dans un composant `UiForm`
 </template>
 
 <script setup lang="ts">
-import { type Ref, ref } from '@vue/reactivity'
+import type { PropType, Ref } from 'vue'
+import { ref } from 'vue'
 import { useFieldViolation } from '~/composables/form/useFieldViolation'
-import type { PropType } from '@vue/runtime-core'
+
+type ValidationRule = (value: string | number | null) => boolean | string
 
 const props = defineProps({
   /**
@@ -82,7 +84,7 @@ const props = defineProps({
    * @see https://vuetify.cn/en/components/forms/#validation-with-submit-clear
    */
   rules: {
-    type: Array as PropType<any[]>,
+    type: Array as PropType<ValidationRule[]>,
     required: false,
     default: () => [],
   },

+ 4 - 4
components/Ui/ItemFromUri.vue

@@ -14,12 +14,12 @@ Espace permettant de récupérer un item via une uri et de gérer son affichage
 <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
 
-import { Query } from 'pinia-orm'
+import type { Query } from 'pinia-orm'
+import { computed } from 'vue'
+import type { ComputedRef } from 'vue'
 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 type ApiResource from '~/models/ApiResource'
 
 const props = defineProps({
   uri: {

+ 1 - 2
components/Ui/SystemBar.vue

@@ -15,7 +15,7 @@ System bars
 </template>
 
 <script setup lang="ts">
-import {useDisplay} from 'vuetify';
+import { useDisplay } from 'vuetify'
 
 const props = defineProps({
   text: {
@@ -36,7 +36,6 @@ const props = defineProps({
 })
 </script>
 
-
 <style scoped lang="scss">
 .alert-bar {
   position: relative;

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

@@ -7,9 +7,13 @@ Template de base d'un tableau interactif
 <template>
   <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">
-        <slot :name="header.item" v-bind="props">
-          {{ props.item[header.value] }}
+      <template
+        v-for="(header, index) in headersWithItem"
+        :key="index"
+        #[header.item]="slotProps"
+      >
+        <slot :name="header.item" v-bind="slotProps">
+          {{ slotProps.item[header.value] }}
         </slot>
       </template>
     </v-data-table>
@@ -17,13 +21,19 @@ Template de base d'un tableau interactif
 </template>
 
 <script setup lang="ts">
+interface TableHeader {
+  value: string
+  item?: string
+  [key: string]: unknown
+}
+
 const props = defineProps({
   items: {
     type: Array,
     required: true,
   },
   headers: {
-    type: Array,
+    type: Array as PropType<TableHeader[]>,
     required: true,
   },
 })
@@ -31,7 +41,7 @@ const props = defineProps({
 const { headers } = toRefs(props)
 
 const headersWithItem = computed(() => {
-  return headers.value.map((header: any) => {
+  return headers.value.map((header: TableHeader) => {
     header.item = 'item.' + header.value
     return header
   })

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

@@ -7,9 +7,9 @@ Date formatée
 </template>
 
 <script setup lang="ts">
+import { computed } from 'vue'
+import type { ComputedRef } from 'vue'
 import DateUtils from '~/services/utils/dateUtils'
-import { computed } from '@vue/reactivity'
-import type { ComputedRef } from '@vue/reactivity'
 
 const props = defineProps({
   data: {

+ 3 - 3
components/Ui/Xeditable/Text.vue

@@ -8,7 +8,7 @@ Utilisé par exemple pour le choix de l'année active
   <main>
     <!-- Mode édition activé -->
     <div v-if="edit" class="d-flex align-center x-editable-input">
-      <UiInputText class="ma-0 pa-0" :type="type" v-model="inputValue" />
+      <UiInputText v-model="inputValue" class="ma-0 pa-0" :type="type" />
 
       <v-icon
         icon="fas fa-check"
@@ -34,8 +34,8 @@ Utilisé par exemple pour le choix de l'année active
 </template>
 
 <script setup lang="ts">
-import { ref } from '@vue/reactivity'
-import type { Ref } from '@vue/reactivity'
+import { ref } from 'vue'
+import type { Ref } from 'vue'
 
 const props = defineProps({
   type: {

+ 1 - 1
composables/data/useAp2iRequestService.ts

@@ -43,7 +43,7 @@ export const useAp2iRequestService = () => {
     options.headers = headers
 
     pending.value = true
-    console.log('Request : ' + request + ' (SSR: ' + process.server + ')')
+    console.log('Request : ' + request + ' (SSR: ' + import.meta.server + ')')
   }
 
   const onRequestError = function (_: FetchContext) {

+ 1 - 1
composables/data/useApiLegacyRequestService.ts

@@ -29,7 +29,7 @@ export const useApiLegacyRequestService = () => {
    * @param request
    * @param options
    */
-  const onRequest = function ({ request, options }: FetchContext) {
+  const onRequest = function ({ request: _request, options }: FetchContext) {
     // @ts-expect-error options is not aware of noXaccessId
     if (options && options.noXaccessId) {
       return

+ 2 - 2
composables/data/useEntityFetch.ts

@@ -6,9 +6,9 @@ import type {
   AsyncDataRequestStatus,
 } from '#app/composables/asyncData'
 import { useEntityManager } from '~/composables/data/useEntityManager'
-import ApiResource from '~/models/ApiResource'
+import type ApiResource from '~/models/ApiResource'
 import type { Collection } from '~/types/data'
-import Query from '~/services/data/Query'
+import type Query from '~/services/data/Query'
 
 interface useEntityFetchReturnType {
   fetch: (

+ 4 - 5
composables/data/useImageFetch.ts

@@ -2,13 +2,13 @@ import type { AsyncData } from '#app'
 import { v4 as uuid4 } from 'uuid'
 import type { Ref } from 'vue'
 import { useImageManager } from '~/composables/data/useImageManager'
+import { IMAGE_SIZE } from '~/types/enum/enums'
 
 interface useImageFetchReturnType {
   fetch: (
     id: Ref<number | null>,
+    size?: IMAGE_SIZE,
     defaultImage?: string | null,
-    height?: number,
-    width?: number,
   ) => AsyncData<string | ArrayBuffer | null, Error | null>
 }
 
@@ -20,13 +20,12 @@ export const useImageFetch = (): useImageFetchReturnType => {
 
   const fetch = (
     id: Ref<number | null>, // If id is null, fetch shall return the default image url
+    size: IMAGE_SIZE = IMAGE_SIZE.MD,
     defaultImage: string | null = null,
-    height: number = 0,
-    width: number = 0,
   ) =>
     useAsyncData(
       'img' + (id ?? defaultImage ?? 0) + '_' + uuid4(),
-      () => imageManager.get(id.value, defaultImage, height, width),
+      () => imageManager.get(id.value, size, defaultImage),
       { lazy: true, server: false }, // Always fetch images client-side
     )
 

+ 1 - 2
composables/utils/useDownloadFile.ts

@@ -1,6 +1,6 @@
 import FileSaver from 'file-saver'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
-import File from '~/models/Core/File'
+import type File from '~/models/Core/File'
 
 export const useDownloadFile = async (file: File) => {
   const { apiRequestService } = useAp2iRequestService()
@@ -17,6 +17,5 @@ export const useDownloadFile = async (file: File) => {
 
   const blob = new Blob([blobPart], { type: response.type })
 
-  // eslint-disable-next-line import/no-named-as-default-member
   FileSaver.saveAs(blob, file.name ?? 'unknown')
 }

+ 0 - 2
composables/utils/useDownloadFromRoute.ts

@@ -1,6 +1,5 @@
 import FileSaver from 'file-saver'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
-import File from '~/models/Core/File'
 
 /**
  * Permet de télécharger un fichier fourni par la route donnée
@@ -19,6 +18,5 @@ export const useDownloadFromRoute = async (route: string, filename: string) => {
 
   const blob = new Blob([response], { type: response.type })
 
-  // eslint-disable-next-line import/no-named-as-default-member
   FileSaver.saveAs(blob, filename)
 }

+ 6 - 2
composables/utils/useRedirect.ts

@@ -4,7 +4,9 @@ export const useRedirect = () => {
   const runtimeConfig = useRuntimeConfig()
 
   const redirectToLogout = () => {
-    const baseUrl = runtimeConfig.baseUrlAdminLegacy ?? runtimeConfig.public.baseUrlAdminLegacy
+    const baseUrl =
+      runtimeConfig.baseUrlAdminLegacy ??
+      runtimeConfig.public.baseUrlAdminLegacy
 
     if (!baseUrl) {
       throw new Error('Configuration error : no redirection target')
@@ -15,7 +17,9 @@ export const useRedirect = () => {
   }
 
   const redirectToHome = () => {
-    const baseUrl = runtimeConfig.baseUrlAdminLegacy ?? runtimeConfig.public.baseUrlAdminLegacy
+    const baseUrl =
+      runtimeConfig.baseUrlAdminLegacy ??
+      runtimeConfig.public.baseUrlAdminLegacy
 
     if (!baseUrl) {
       throw new Error('Configuration error : no redirection target')

+ 9 - 0
config/abilities/pages/billing.yaml

@@ -93,3 +93,12 @@ afi_page:
         function: accessHasAnyRoleAbility,
         parameters: [{ action: 'manage', subject: 'billings-administration' }],
       }
+
+sdd_regie_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['SddRegie'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'manage', subject: 'billings-administration' }],
+      }

+ 33 - 0
config/abilities/pages/parameters.yaml

@@ -254,3 +254,36 @@ import_page:
       parameters:
         - { action: 'manage', subject: 'user' }
         - { action: 'manage', subject: 'equipments' }
+
+parcours_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['PedagogicsAdministation'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'pedagogics-administration' }],
+      }
+
+family_quotient_models_page:
+  action: 'display'
+  conditions:
+    - {
+        function: organizationHasAnyModule,
+        parameters: ['BillingAdministration'],
+      }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'billings-seizure' }],
+      }
+
+pseudonymization_page:
+  action: 'display'
+  conditions:
+    - { function: organizationHasAnyModule, parameters: ['GeneralConfig'] }
+    - {
+        function: accessHasAnyRoleAbility,
+        parameters: [{ action: 'read', subject: 'organization' }],
+      }

+ 2 - 2
env/setupEnv.mjs

@@ -32,8 +32,8 @@ const environments = {
   ci: '.env.ci',
 }
 
-if (!environments.hasOwnProperty(hostname)) {
-  throw Error('Critical : unknown environment [' + hostname + ']')
+if (!Object.prototype.hasOwnProperty.call(environments, hostname)) {
+  throw new Error('Critical : unknown environment [' + hostname + ']')
 }
 
 const targetEnvFile = path.join(projectDir, 'env', environments[hostname])

+ 41 - 40
eslint.config.mjs

@@ -1,21 +1,15 @@
-import vue from 'eslint-plugin-vue'
-import typescriptEslint from '@typescript-eslint/eslint-plugin'
 import globals from 'globals'
-import parser from 'vue-eslint-parser'
-import path from 'node:path'
-import { fileURLToPath } from 'node:url'
-import js from '@eslint/js'
-import { FlatCompat } from '@eslint/eslintrc'
+import withNuxt from './.nuxt/eslint.config.mjs'
 
-const __filename = fileURLToPath(import.meta.url)
-const __dirname = path.dirname(__filename)
-const compat = new FlatCompat({
-  baseDirectory: __dirname,
-  recommendedConfig: js.configs.recommended,
-  allConfig: js.configs.all,
-})
+// Polyfill for structuredClone if it doesn't exist
+if (typeof structuredClone !== 'function') {
+  globalThis.structuredClone = function (obj) {
+    return JSON.parse(JSON.stringify(obj))
+  }
+}
 
-export default [
+// Configuration de base personnalisée
+const customConfig = [
   {
     ignores: [
       '**/.nuxt',
@@ -25,21 +19,7 @@ export default [
       'models/models.ts',
     ],
   },
-  ...compat.extends(
-    '@nuxtjs/eslint-config-typescript',
-    'plugin:nuxt/recommended',
-    'eslint:recommended',
-    'plugin:@typescript-eslint/recommended',
-    'plugin:vue/vue3-recommended',
-    'plugin:prettier/recommended',
-    'plugin:you-dont-need-lodash-underscore/compatible',
-  ),
   {
-    plugins: {
-      vue,
-      '@typescript-eslint': typescriptEslint,
-    },
-
     languageOptions: {
       globals: {
         ...globals.browser,
@@ -58,29 +38,50 @@ export default [
         watch: 'readonly',
         useRepo: 'readonly',
       },
-
-      parser: parser,
-      ecmaVersion: 2020,
-      sourceType: 'module',
-
-      parserOptions: {
-        parser: '@typescript-eslint/parser',
-        tsconfigRootDir: '/home/workspace',
-      },
     },
 
     rules: {
       'no-console': 0,
-
       'vue/valid-v-slot': [
         'error',
         {
           allowModifiers: true,
         },
       ],
-
       'vue/multi-word-component-names': 0,
       '@typescript-eslint/no-inferrable-types': 0,
+      '@typescript-eslint/no-extraneous-class': 0,
+    },
+  },
+  // Directory-specific configurations
+  {
+    files: ['**/*.vue'],
+    rules: {
+      '@typescript-eslint/no-unused-vars': 0,
+    },
+  },
+  {
+    files: ['layouts/**/*.vue'],
+    rules: {
+      'vue/multi-word-component-names': 0,
+    },
+  },
+  {
+    files: ['pages/**/*.vue'],
+    rules: {
+      'vue/multi-word-component-names': 0,
+    },
+  },
+  {
+    files: ['tests/**/*'],
+    rules: {
+      '@typescript-eslint/ban-ts-comment': 0,
+      '@typescript-eslint/no-unused-vars': 0,
+      '@typescript-eslint/no-explicit-any': 0,
+      'require-await': 0,
     },
   },
 ]
+
+// Utiliser withNuxt avec l'option standalone: false pour éviter les conflits
+export default withNuxt(customConfig)

+ 8 - 2
i18n/lang/fr.json

@@ -20,6 +20,7 @@
   "access_rewards_command": "Commande de distinctions",
   "access_rewards": "Distinctions",
   "afi_export": "Export AFI",
+  "sdd_regie_export": "Export prélèvements SDD Régie",
   "item": "Détails",
   "organization_breadcrumbs": "Fiche de la structure",
   "subscription_breadcrumbs": "Mon abonnement",
@@ -39,7 +40,7 @@
   "subdomain_need_to_have_0_to_60_cars": "Le sous-domaine doit comporter de 2 à 60 caractères",
   "this_subdomain_is_already_in_use": "Ce sous-domaine est déjà utilisé",
   "this_subdomain_is_available": "Ce sous-domaine est disponible",
-  "subdomain_can_not_contain_spaces_or_special_cars": "Le sous-domaine ne doit pas contenir d'espaces ni de caractères spéciaux",
+  "subdomain_can_not_contain_spaces_caps_or_special_cars": "Le sous-domaine ne doit pas contenir d'espaces, de majuscules ni de caractères spéciaux",
   "please_enter_a_value": "Veuillez saisir une valeur",
   "please_enter_a_value_for_the_subdomain": "Veuillez saisir une valeur pour le sous-domaine",
   "validation_ongoing": "Validation en cours",
@@ -246,6 +247,7 @@
   "otherWebsite": "Autre site web",
   "newSubDomain": "Nouveau sous domaine",
   "yourSubdomains": "Vos sous-domaines",
+  "a_short_subdomains_definition": "Le sous-domaine est la première partie de l'adresse de votre site web Opentalent.",
   "timezone": "Fuseau horaire",
   "qrCode": "QrCode",
   "qrCodeForLicence": "QrCode pour la licence",
@@ -522,6 +524,9 @@
   "cmf_licence_details_url": "Consulter les avantages de la licence CMF",
   "generate": "Générer",
   "parameters": "Préférences",
+  "parcours": "Parcours",
+  "family_quotient_models": "Modèles de quotients familiaux",
+  "pseudonymization": "Pseudonymisation",
   "parameters_page": "Préférences",
   "places": "Lieux",
   "education": "Enseignements",
@@ -757,5 +762,6 @@
   "trial_ongoing": "En cours d'essai",
   "try_premium_version": "Essayer la version premium",
   "subscribe_to_the_offer": "Souscrire à l'offre",
-  "to_know_more": "En savoir plus"
+  "to_know_more": "En savoir plus",
+  "placeListMenuKey": "Lieu"
 }

+ 0 - 6
layouts/.eslintrc.cjs

@@ -1,6 +0,0 @@
-/** On désactive cette règle pour les pages et les layouts seulement : https://eslint.vuejs.org/rules/multi-word-component-names.html */
-module.exports = {
-  rules: {
-    'vue/multi-word-component-names': 0,
-  },
-}

+ 1 - 1
layouts/error.vue

@@ -21,7 +21,7 @@ const props = defineProps({
 })
 
 if (
-  process.client &&
+  import.meta.client &&
   props.error.statusCode === 404 &&
   process.env.NODE_ENV === 'production'
 ) {

+ 0 - 1
layouts/parameters.vue

@@ -38,7 +38,6 @@ const route = useRoute()
 const i18n = useI18n()
 
 const pageTitle = computed(() => i18n.t(route.name || 'parameters_page'))
-
 </script>
 
 <style scoped lang="scss">

+ 2 - 2
models/Access/MyProfile.ts

@@ -1,7 +1,7 @@
 import { Num, Uid, Attr, Bool, Str } from 'pinia-orm/dist/decorators'
 import type { Historical } from '~/types/interfaces'
-import Access from '~/models/Access/Access'
-import OrganizationProfile from '~/models/Organization/OrganizationProfile'
+import type Access from '~/models/Access/Access'
+import type OrganizationProfile from '~/models/Organization/OrganizationProfile'
 import ApiResource from '~/models/ApiResource'
 
 /**

+ 0 - 1
models/ApiResource.ts

@@ -4,7 +4,6 @@ import { Model } from 'pinia-orm'
  * Base class for resources that can be fetched from the API
  */
 class ApiResource extends Model {
-  // eslint-disable-next-line no-use-before-define
   protected static _iriEncodedFields: Record<string, ApiResource>
   protected static _idField: string
 

+ 1 - 1
models/Core/Notification.ts

@@ -1,5 +1,5 @@
 import { Str, Uid, Attr } from 'pinia-orm/dist/decorators'
-import NotificationMessage from '~/models/Core/NotificationMessage'
+import type NotificationMessage from '~/models/Core/NotificationMessage'
 import ApiModel from '~/models/ApiModel'
 
 /**

+ 1 - 5
models/Custom/Search/UserSearchItem.ts

@@ -1,9 +1,5 @@
-import { Num, Uid, Attr, Str } from 'pinia-orm/dist/decorators'
-import type { Historical } from '~/types/interfaces'
-import Person from '~/models/Person/Person'
+import { Uid, Str } from 'pinia-orm/dist/decorators'
 import ApiModel from '~/models/ApiModel'
-import { IriEncoded } from '~/models/decorators'
-import Organization from '~/models/Organization/Organization'
 
 /**
  * AP2i Model : UserSearchItem

+ 1 - 1
models/Organization/Parameters.ts

@@ -40,7 +40,7 @@ export default class Parameters extends ApiModel {
   declare logoDonorsMove: boolean
 
   @Str(null)
-  declare otherWebsite: string | null
+  declare website: string | null
 
   @Str(null)
   declare customDomain: string | null

+ 3 - 5
models/decorators.ts

@@ -1,4 +1,4 @@
-import ApiResource from '~/models/ApiResource'
+import type ApiResource from '~/models/ApiResource'
 
 /**
  * Decorates an ApiResource's property to signal it as a field that is provided
@@ -9,8 +9,7 @@ import ApiResource from '~/models/ApiResource'
  */
 export function IriEncoded(apiResource: typeof ApiResource): PropertyDecorator {
   // We have to comply with the PropertyDecorator return type
-  // eslint-disable-next-line @typescript-eslint/ban-types
-  return (target: Object, propertyKey: string | symbol) => {
+  return (target: object, propertyKey: string | symbol) => {
     // @ts-expect-error The object is an ApiResource
     const self = target.$self()
 
@@ -27,8 +26,7 @@ export function IriEncoded(apiResource: typeof ApiResource): PropertyDecorator {
  */
 export function IdField(): PropertyDecorator {
   // We have to comply with the PropertyDecorator return type
-  // eslint-disable-next-line @typescript-eslint/ban-types
-  return (target: Object, propertyKey: string | symbol) => {
+  return (target: object, propertyKey: string | symbol) => {
     // @ts-expect-error The object is an ApiResource
     const self = target.$self()
 

+ 8 - 9
nuxt.config.ts

@@ -125,20 +125,19 @@ export default defineNuxtConfig({
     },
   },
 
- css: [
-   '@/assets/css/global.scss',
-   '@/assets/css/theme.scss',
-   '@/assets/css/import.scss',
-   '@vuepic/vue-datepicker/dist/main.css',
-   '@/assets/css/vue-date-picker.scss',
- ],
+  css: [
+    '@/assets/css/global.scss',
+    '@/assets/css/theme.scss',
+    '@/assets/css/import.scss',
+    '@vuepic/vue-datepicker/dist/main.css',
+    '@/assets/css/vue-date-picker.scss',
+  ],
 
   typescript: {
     strict: true,
   },
 
   modules: [
-    // eslint-disable-next-line require-await
     async (_, nuxt) => {
       nuxt.hooks.hook('vite:extendConfig', (config) =>
         // @ts-expect-error A revoir après que les lignes aient été décommentées
@@ -170,8 +169,8 @@ export default defineNuxtConfig({
     '@nuxt/image',
     'nuxt-prepare',
     'nuxt-vitalizer',
+    '@nuxt/eslint',
   ],
-
   vite: {
     esbuild: {
       drop: process.env.DEBUG ? [] : ['console', 'debugger'],

+ 2 - 4
package.json

@@ -28,7 +28,9 @@
     "@casl/vue": "2.2.2",
     "@fortawesome/fontawesome-free": "^6.7.2",
     "@mdi/font": "^7.4.47",
+    "@nuxt/eslint": "1.4.1",
     "@nuxt/image": "1.9.0",
+    "@nuxtjs/eslint-config-typescript": "^12.1.0",
     "@nuxtjs/i18n": "^9.1.3",
     "@pinia-orm/nuxt": "^1.10.1",
     "@pinia/nuxt": "^0.5.1",
@@ -61,9 +63,6 @@
     "@nuxt/devtools": "^1.7.0",
     "@nuxt/test-utils": "^3.15.4",
     "@nuxt/test-utils-edge": "3.8.0-28284309.b3d3d7f4",
-    "@nuxtjs/eslint-config": "^12.0.0",
-    "@nuxtjs/eslint-config-typescript": "^12.1.0",
-    "@nuxtjs/eslint-module": "^4.1.0",
     "@types/cleave.js": "^1.4.12",
     "@types/event-source-polyfill": "^1.0.5",
     "@types/file-saver": "^2.0.7",
@@ -77,7 +76,6 @@
     "@typescript-eslint/parser": "^8.22.0",
     "@vitejs/plugin-vue": "^5.2.1",
     "@vitest/coverage-v8": "3.0.4",
-    "@vue/eslint-config-standard": "^9.0.0",
     "@vue/test-utils": "^2.4.6",
     "blob-polyfill": "^9.0.20240710",
     "eslint": "^9.19.0",

+ 0 - 6
pages/.eslintrc.cjs

@@ -1,6 +0,0 @@
-/** On désactive cette règle pour les pages et les layouts seulement : https://eslint.vuejs.org/rules/multi-word-component-names.html */
-module.exports = {
-  rules: {
-    'vue/multi-word-component-names': 0,
-  },
-}

+ 0 - 1
pages/parameters.vue

@@ -16,7 +16,6 @@ definePageMeta({
   name: 'parameters_page',
   layout: false,
 })
-
 </script>
 
 <style scoped lang="scss">

+ 1 - 1
pages/parameters/attendance_booking_reasons/new.vue

@@ -8,7 +8,7 @@
         <template #default="{ entity }">
           <v-container :fluid="true" class="container">
             <v-row>
-              <v-col cols="12" sm="6"> </v-col>
+              <v-col cols="12" sm="6" />
             </v-row>
             <v-row>
               <v-col cols="12" sm="6">

+ 38 - 39
pages/parameters/attendances.vue

@@ -1,47 +1,46 @@
 <template>
-  <LayoutParametersSection v-if="organizationProfile.isSchool">
-    <h4>{{ $t("alert_configuration") }}</h4>
-    <UiLoadingPanel v-if="pending" />
-    <UiForm
-      v-else-if="parameters !== null"
-      v-model="parameters"
-    >
-      <v-row>
-        <v-col cols="12">
-          <UiInputCheckbox
-            v-model="parameters.sendAttendanceEmail"
-            field="sendAttendanceEmail"
-            label="sendAttendanceEmail"
-          />
+  <div>
+    <LayoutParametersSection v-if="organizationProfile.isSchool">
+      <h4>{{ $t('alert_configuration') }}</h4>
+      <UiLoadingPanel v-if="pending" />
+      <UiForm v-else-if="parameters !== null" v-model="parameters">
+        <v-row>
+          <v-col cols="12">
+            <UiInputCheckbox
+              v-model="parameters.sendAttendanceEmail"
+              field="sendAttendanceEmail"
+              label="sendAttendanceEmail"
+            />
 
-          <UiInputCheckbox
-            v-model="parameters.sendAttendanceSms"
-            field="sendAttendanceSms"
-          />
+            <UiInputCheckbox
+              v-model="parameters.sendAttendanceSms"
+              field="sendAttendanceSms"
+            />
 
-          <UiInputCheckbox
-            v-model="parameters.notifyAdministrationAbsence"
-            field="notifyAdministrationAbsence"
-          />
+            <UiInputCheckbox
+              v-model="parameters.notifyAdministrationAbsence"
+              field="notifyAdministrationAbsence"
+            />
 
-          <UiInputNumber
-            v-if="parameters.notifyAdministrationAbsence"
-            v-model="parameters.numberConsecutiveAbsences"
-            field="numberConsecutiveAbsences"
-            :rules="rules()"
-          />
-        </v-col>
-      </v-row>
-    </UiForm>
-  </LayoutParametersSection>
+            <UiInputNumber
+              v-if="parameters.notifyAdministrationAbsence"
+              v-model="parameters.numberConsecutiveAbsences"
+              field="numberConsecutiveAbsences"
+              :rules="rules()"
+            />
+          </v-col>
+        </v-row>
+      </UiForm>
+    </LayoutParametersSection>
 
-  <LayoutParametersSection>
-    <LayoutParametersEntityTable
-      :model="AttendanceBookingReason"
-      :title="$t('attendanceBookingReasons')"
-      :columns-definitions="[{ property: 'reason' }]"
-    />
-  </LayoutParametersSection>
+    <LayoutParametersSection>
+      <LayoutParametersEntityTable
+        :model="AttendanceBookingReason"
+        :title="$t('attendanceBookingReasons')"
+        :columns-definitions="[{ property: 'reason' }]"
+      />
+    </LayoutParametersSection>
+  </div>
 </template>
 <script setup lang="ts">
 import type { AsyncData } from '#app'

+ 1 - 4
pages/parameters/bulletin.vue

@@ -2,10 +2,7 @@
   <LayoutContainer>
     <LayoutParametersSection>
       <UiLoadingPanel v-if="pending" />
-      <UiForm
-        v-else
-        v-model="parameters"
-      >
+      <UiForm v-else v-model="parameters">
         <v-row>
           <v-col cols="12">
             <h4 class="mb-8">{{ $t('itemsToDisplayOnBulletins') }}</h4>

+ 1 - 4
pages/parameters/education_notation.vue

@@ -2,10 +2,7 @@
   <LayoutContainer>
     <LayoutParametersSection>
       <UiLoadingPanel v-if="pending" />
-      <UiForm
-        v-else
-        v-model="parameters"
-      >
+      <UiForm v-else v-model="parameters">
         <v-row>
           <v-col cols="12">
             <UiInputCheckbox

+ 1 - 1
pages/parameters/education_timings/index.vue

@@ -1,6 +1,6 @@
 <template>
   <LayoutContainer>
-      <LayoutParametersSection>
+    <LayoutParametersSection>
       <LayoutParametersEntityTable
         :model="EducationTiming"
         :columns-definitions="[{ property: 'timing' }]"

+ 1 - 1
pages/parameters/education_timings/new.vue

@@ -8,7 +8,7 @@
         <template #default="{ entity }">
           <v-container :fluid="true" class="container">
             <v-row>
-              <v-col cols="12" sm="6"> </v-col>
+              <v-col cols="12" sm="6" />
             </v-row>
             <v-row>
               <v-col cols="12" sm="6">

+ 8 - 7
pages/parameters/general_parameters.vue

@@ -2,14 +2,14 @@
   <LayoutContainer>
     <LayoutParametersSection>
       <UiLoadingPanel v-if="pending" />
-      <UiForm
-        v-else-if="parameters !== null"
-        v-model="parameters"
-      >
+      <UiForm v-else-if="parameters !== null" v-model="parameters">
         <v-row>
           <v-col cols="12">
             <UiInputDatePicker
-              v-if="organizationProfile.isSchool"
+              v-if="
+                organizationProfile.isSchool ||
+                organizationProfile.isManagerProduct
+              "
               v-model="parameters.financialDate"
               field="financialDate"
               label="start_date_of_financial_season"
@@ -58,7 +58,8 @@
 
             <UiInputCheckbox
               v-if="
-                organizationProfile.isSchool && organizationProfile.isAssociation
+                organizationProfile.isSchool &&
+                organizationProfile.isAssociation
               "
               v-model="parameters.studentsAreAdherents"
               field="studentsAreAdherents"
@@ -76,7 +77,7 @@
                 v-model="parameters.qrCode"
                 field="qrCode"
                 :width="120"
-                :cropping-enabled="false"
+                :cropping-enabled="true"
               />
             </div>
           </v-col>

+ 3 - 6
pages/parameters/intranet.vue

@@ -2,13 +2,10 @@
   <LayoutContainer>
     <LayoutParametersSection>
       <UiLoadingPanel v-if="pending" />
-      <UiForm
-        v-else
-        v-model="parameters"
-      >
+      <UiForm v-else v-model="parameters">
         <v-row>
           <v-col cols="12">
-            <h4 class="mb-4">{{ $t('teachers')}}</h4>
+            <h4 class="mb-4">{{ $t('teachers') }}</h4>
 
             <UiInputCheckbox
               v-model="parameters.createCourse"
@@ -34,7 +31,7 @@
               label="allow_teachers_to_generate_attendance_reports"
             />
 
-            <h4 class="mt-3 mb-4">{{ $t('pupils-members')}}</h4>
+            <h4 class="mt-3 mb-4">{{ $t('pupils-members') }}</h4>
             <UiInputCheckbox
               v-model="parameters.administrationCc"
               field="administrationCc"

+ 1 - 5
pages/parameters/sms.vue

@@ -4,10 +4,7 @@
       <UiForm v-if="parameters" v-model="parameters">
         <v-row>
           <v-col cols="12">
-            <UiInputText
-              v-model="parameters.usernameSMS"
-              field="usernameSMS"
-            />
+            <UiInputText v-model="parameters.usernameSMS" field="usernameSMS" />
           </v-col>
           <v-col cols="12">
             <UiInputText
@@ -62,7 +59,6 @@ const rules = () => [
 </script>
 
 <style scoped lang="scss">
-
 /**
 Simule une apparence de saisie de type mot de passe
 Sans ça, les navigateurs proposent la saisie semi auto et la mémorisation du mot de passe

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