浏览代码

merge release/2.5

Olivier Massot 6 月之前
父节点
当前提交
22b29f72c4
共有 100 个文件被更改,包括 772 次插入702 次删除
  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

部分文件因为文件数量过多而无法显示