Browse Source

fix merge conflicts

Olivier Massot 9 months ago
parent
commit
e35a052b1c
85 changed files with 1749 additions and 1325 deletions
  1. 14 0
      assets/css/global.scss
  2. 28 0
      assets/css/vue-date-picker.scss
  3. 56 12
      components/Layout/AlertBar.vue
  4. 0 1
      components/Layout/AlertBar/Env.vue
  5. 46 24
      components/Layout/Header.vue
  6. 24 0
      components/Layout/Header/Title.vue
  7. 9 2
      components/Layout/Header/UniversalCreation/Card.vue
  8. 36 21
      components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue
  9. 14 7
      components/Layout/MainMenu.vue
  10. 7 0
      components/Layout/Parameters/EntityTable.vue
  11. 67 35
      components/Layout/Parameters/Menu.vue
  12. 19 0
      components/Layout/Parameters/Section.vue
  13. 108 42
      components/Layout/Parameters/Table.vue
  14. 93 0
      components/Layout/Parameters/Website/ActivationSwitch.vue
  15. 1 1
      components/Layout/SubHeader/ActivityYear.vue
  16. 7 1
      components/Layout/SubHeader/DataTimingRange.vue
  17. 1 0
      components/Layout/Subheader.vue
  18. 10 1
      components/Ui/Button/Submit.vue
  19. 65 92
      components/Ui/DatePicker.vue
  20. 0 129
      components/Ui/DateRangePicker.vue
  21. 18 2
      components/Ui/Form.vue
  22. 11 16
      components/Ui/Form/DeletionConfirmationDialog.vue
  23. 2 0
      components/Ui/Image.vue
  24. 18 0
      components/Ui/Input/Autocomplete.vue
  25. 13 0
      components/Ui/Input/Autocomplete/Accesses.vue
  26. 20 2
      components/Ui/Input/Checkbox.vue
  27. 1 0
      components/Ui/Input/Combobox.vue
  28. 49 23
      components/Ui/Input/DatePicker.vue
  29. 4 1
      components/Ui/Input/Email.vue
  30. 1 0
      components/Ui/Input/Enum.vue
  31. 16 2
      components/Ui/Input/Image.vue
  32. 1 6
      components/Ui/Input/Number.vue
  33. 1 0
      components/Ui/Input/Text.vue
  34. 15 26
      components/Ui/SystemBar.vue
  35. 8 4
      composables/utils/useRedirect.ts
  36. 46 26
      i18n/lang/fr.json
  37. 1 1
      layouts/default.vue
  38. 18 8
      layouts/parameters.vue
  39. 7 6
      nuxt.config.ts
  40. 1 1
      package.json
  41. 1 0
      pages/parameters.vue
  42. 8 5
      pages/parameters/attendance_booking_reasons/[id].vue
  43. 8 5
      pages/parameters/attendance_booking_reasons/new.vue
  44. 35 31
      pages/parameters/attendances.vue
  45. 72 63
      pages/parameters/bulletin.vue
  46. 8 5
      pages/parameters/cycles/[id].vue
  47. 47 42
      pages/parameters/education_notation.vue
  48. 8 5
      pages/parameters/education_timings/[id].vue
  49. 6 4
      pages/parameters/education_timings/index.vue
  50. 8 5
      pages/parameters/education_timings/new.vue
  51. 76 72
      pages/parameters/general_parameters.vue
  52. 45 39
      pages/parameters/intranet.vue
  53. 8 5
      pages/parameters/residence_areas/[id].vue
  54. 8 6
      pages/parameters/residence_areas/index.vue
  55. 8 5
      pages/parameters/residence_areas/new.vue
  56. 26 28
      pages/parameters/sms.vue
  57. 8 4
      pages/parameters/subdomains/[id].vue
  58. 8 4
      pages/parameters/subdomains/new.vue
  59. 37 42
      pages/parameters/super_admin.vue
  60. 30 24
      pages/parameters/teaching.vue
  61. 105 139
      pages/parameters/website.vue
  62. 1 1
      services/data/enumManager.ts
  63. 8 9
      services/data/imageManager.ts
  64. 2 2
      services/layout/menuBuilder/abstractMenuBuilder.ts
  65. 1 1
      services/layout/menuBuilder/accessMenuBuilder.ts
  66. 1 1
      services/layout/menuBuilder/accountMenuBuilder.ts
  67. 1 1
      services/layout/menuBuilder/admin2iosMenuBuilder.ts
  68. 1 1
      services/layout/menuBuilder/agendaMenuBuilder.ts
  69. 1 1
      services/layout/menuBuilder/basicomptaMenuBuilder.ts
  70. 1 1
      services/layout/menuBuilder/billingMenuBuilder.ts
  71. 1 1
      services/layout/menuBuilder/communicationMenuBuilder.ts
  72. 2 11
      services/layout/menuBuilder/configurationMenuBuilder.ts
  73. 1 1
      services/layout/menuBuilder/cotisationsMenuBuilder.ts
  74. 1 1
      services/layout/menuBuilder/donorsMenuBuilder.ts
  75. 1 1
      services/layout/menuBuilder/educationalMenuBuilder.ts
  76. 1 1
      services/layout/menuBuilder/equipmentMenuBuilder.ts
  77. 1 1
      services/layout/menuBuilder/mainMenuBuilder.ts
  78. 1 1
      services/layout/menuBuilder/myAccessesMenuBuilder.ts
  79. 1 1
      services/layout/menuBuilder/myFamilyMenuBuilder.ts
  80. 1 1
      services/layout/menuBuilder/parametersMenuBuilder.ts
  81. 1 1
      services/layout/menuBuilder/rewardsMenuBuilder.ts
  82. 1 1
      services/layout/menuBuilder/statsMenuBuilder.ts
  83. 1 1
      services/layout/menuBuilder/websiteAdminMenuBuilder.ts
  84. 1 1
      services/layout/menuBuilder/websiteListMenuBuilder.ts
  85. 301 258
      yarn.lock

+ 14 - 0
assets/css/global.scss

@@ -70,3 +70,17 @@ header .v-toolbar__content {
 .v-application {
 .v-application {
   font-size: 0.9rem;
   font-size: 0.9rem;
 }
 }
+
+h3, h4 {
+  color: rgb(var(--v-theme-on-neutral-soft));
+}
+
+h3 {
+  font-size: 1.3rem;
+  color: rgb(var(--v-theme-on-neutral));
+}
+
+h4 {
+  font-size: 1.1rem;
+  color: rgb(var(--v-theme-on-neutral));
+}

+ 28 - 0
assets/css/vue-date-picker.scss

@@ -0,0 +1,28 @@
+// @see https://vue3datepicker.com/customization/theming/
+// [!] Sass variables overriding does not work in scoped mode
+.dp__theme_light,
+.dp__theme_dark {
+  --dp-background-color: #ffffff;
+  --dp-text-color: #212121;
+  --dp-hover-color: #f3f3f3;
+  --dp-hover-text-color: #212121;
+  --dp-hover-icon-color: #959595;
+  --dp-primary-color: rgb(var(--v-theme-primary)) !important;
+  --dp-primary-text-color: rgb(var(--v-theme-on-primary)) !important;
+  --dp-secondary-color: rgb(var(--v-theme-neutral-strong)) !important;
+  --dp-border-color: #ddd;
+  --dp-menu-border-color: #ddd;
+  --dp-border-color-hover: #aaaeb7;
+  --dp-disabled-color: #f6f6f6;
+  --dp-scroll-bar-background: #f3f3f3;
+  --dp-scroll-bar-color: #959595;
+  --dp-success-color: rgb(var(--v-theme-success)) !important;
+  --dp-success-color-disabled: rgb(var(--v-theme-neutral-strong)) !important;
+  --dp-icon-color: #959595;
+  --dp-danger-color: #ff6f60;
+  --dp-highlight-color: rgba(25, 118, 210, 0.1);
+}
+
+:root {
+  --dp-action-button-height: 35px;
+}

+ 56 - 12
components/Layout/AlertBar.vue

@@ -6,28 +6,72 @@ Contient les différentes barres d'alertes qui s'affichent dans certains cas
 
 
 <template>
 <template>
   <main>
   <main>
-    <LayoutAlertBarEnv />
+    <v-expand-transition>
+      <div v-if="showAlertBars">
+        <LayoutAlertBarEnv style="z-index: 510"/>
 
 
-    <LayoutAlertBarSwitchUser />
+        <LayoutAlertBarSwitchUser style="z-index: 509" />
 
 
-    <client-only>
-      <LayoutAlertBarCotisation
-        v-if="organizationProfile.isCmf && ability.can('manage', 'cotisation')"
-      />
-    </client-only>
+        <client-only>
+          <LayoutAlertBarCotisation
+            v-if="organizationProfile.isCmf && ability.can('manage', 'cotisation')"
+            style="z-index: 508"
+          />
+        </client-only>
 
 
-    <LayoutAlertBarSwitchYear />
-    <LayoutAlertBarSuperAdmin />
-    <LayoutAlertBarRegistrationStatus
-      v-if="organizationProfile.hasModule('IEL')"
-    />
+        <LayoutAlertBarSwitchYear style="z-index: 507"/>
+        <LayoutAlertBarSuperAdmin style="z-index: 506"/>
+        <LayoutAlertBarRegistrationStatus
+          v-if="organizationProfile.hasModule('IEL')"
+          style="z-index: 505"
+        />
+      </div>
+    </v-expand-transition>
+
+    <div v-if="smAndDown">
+      <div
+        class="folded-bar theme-warning"
+        style="z-index: 504"
+        @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')" />
+      </div>
+    </div>
   </main>
   </main>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 import { useAbility } from '@casl/vue'
 import { useAbility } from '@casl/vue'
+import {useDisplay} from 'vuetify';
+import {StencilPreview} from 'vue-advanced-cropper';
 
 
 const organizationProfile = useOrganizationProfileStore()
 const organizationProfile = useOrganizationProfileStore()
 const ability = useAbility()
 const ability = useAbility()
+const { mdAndUp, smAndDown } = useDisplay()
+
+const unfoldWarnings = ref(false)
+
+const onFoldedWarningClick = () => {
+  unfoldWarnings.value = !unfoldWarnings.value
+}
+
+const showAlertBars = computed(() => mdAndUp.value || unfoldWarnings.value)
+
 </script>
 </script>
+
+<style scoped lang="scss">
+.folded-bar {
+  position: relative;
+  font-size: 14px;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+  padding: 12px;
+  cursor: pointer;
+}
+</style>

+ 0 - 1
components/Layout/AlertBar/Env.vue

@@ -10,7 +10,6 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur n'est pas dans un environneme
     :text="$t('not_production_environment', { env: env })"
     :text="$t('not_production_environment', { env: env })"
     icon="fas fa-exclamation-triangle"
     icon="fas fa-exclamation-triangle"
     class="theme-warning"
     class="theme-warning"
-    style="z-index: 1005"
   />
   />
   <!--
   <!--
   Le z-index est précisé pour éviter cette erreur : https://github.com/vuetifyjs/nuxt-module/issues/205
   Le z-index est précisé pour éviter cette erreur : https://github.com/vuetifyjs/nuxt-module/issues/205

+ 46 - 24
components/Layout/Header.vue

@@ -7,28 +7,24 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
   <v-app-bar order="0" density="compact" class="theme-primary">
   <v-app-bar order="0" density="compact" class="theme-primary">
     <template #prepend>
     <template #prepend>
       <v-app-bar-nav-icon
       <v-app-bar-nav-icon
-        v-if="hasMainMenu && layoutStore.name !== 'parameters'"
-        :icon="isMainMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
-        @click="toggleMainMenu"
+        v-if="hasLateralMenu"
+        :icon="isLateralMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
+        @click="toggleLateralMenu"
       />
       />
-      <div v-else-if="hasParametersMenu && layoutStore.name === 'parameters'">
-        <v-app-bar-nav-icon v-if="mdAndUp" icon="fa fa-gear" />
-        <v-app-bar-nav-icon
-          v-else
-          :icon="isParametersMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
-          @click="toggleParametersMenu"
-        />
-      </div>
     </template>
     </template>
 
 
-    <v-toolbar-title v-if="mdAndUp" v-text="title" />
+    <v-toolbar-title v-if="smAndUp">
+      <LayoutHeaderTitle>
+        {{ title }}
+      </LayoutHeaderTitle>
+    </v-toolbar-title>
 
 
     <LayoutThemeSwitcher v-if="false" />
     <LayoutThemeSwitcher v-if="false" />
     <!-- En attente validation PO -->
     <!-- En attente validation PO -->
 
 
     <LayoutHeaderUniversalCreationCreateButton
     <LayoutHeaderUniversalCreationCreateButton
       v-if="showUniversalButton"
       v-if="showUniversalButton"
-      class="mr-3"
+      :class="smAndUp ? 'mr-3' : ''"
     />
     />
 
 
     <LayoutHeaderHomeBtn v-if="smAndUp" />
     <LayoutHeaderHomeBtn v-if="smAndUp" />
@@ -47,7 +43,7 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
 
 
     <a
     <a
       :href="runtimeConfig.supportUrl || runtimeConfig.public.supportUrl"
       :href="runtimeConfig.supportUrl || runtimeConfig.public.supportUrl"
-      class="text-body px-3 py-4 ml-2 theme-secondary text-decoration-none h-100"
+      class="help theme-secondary"
       target="_blank"
       target="_blank"
     >
     >
       <span class="d-none d-sm-none d-md-flex">
       <span class="d-none d-sm-none d-md-flex">
@@ -79,15 +75,29 @@ const title: ComputedRef<string> = computed(
 
 
 const { hasMenu, isMenuOpened, toggleMenu } = useMenu()
 const { hasMenu, isMenuOpened, toggleMenu } = useMenu()
 
 
-const { smAndUp, mdAndUp } = useDisplay()
-
-const hasMainMenu = computed(() => hasMenu('Main'))
-const isMainMenuOpened = computed(() => isMenuOpened('Main'))
-const toggleMainMenu = () => toggleMenu('Main')
-
-const hasParametersMenu = computed(() => hasMenu('Parameters'))
-const isParametersMenuOpened = computed(() => isMenuOpened('Parameters'))
-const toggleParametersMenu = () => toggleMenu('Parameters')
+const { smAndUp } = useDisplay()
+
+const hasLateralMenu = computed(() => {
+  return (
+    (layoutStore.name !== 'parameters' && hasMenu('Main')) ||
+    (layoutStore.name === 'parameters' && hasMenu('Parameters'))
+  )
+})
+
+const isLateralMenuOpened = computed(() => {
+  return (
+    (layoutStore.name !== 'parameters' && isMenuOpened('Main')) ||
+    (layoutStore.name === 'parameters' && isMenuOpened('Parameters'))
+  )
+})
+
+const toggleLateralMenu = () => {
+  if (layoutStore.name === 'parameters') {
+    toggleMenu('Parameters')
+  } else {
+    toggleMenu('Main')
+  }
+}
 
 
 const ability = useAbility()
 const ability = useAbility()
 const showUniversalButton =
 const showUniversalButton =
@@ -106,9 +116,21 @@ const layoutStore = useLayoutStore()
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
+
+:deep(.v-toolbar__content > .v-toolbar-title) {
+  margin-left: 2px;
+
+  .v-img {
+    background-color: rgb(var(--v-theme-on-primary));
+    border-radius: 18px;
+  }
+}
+
 .help {
 .help {
-  padding: 14px 14px 13px;
+  padding: 12px 16px;
+  margin-left: 4px;
   font-size: 14px;
   font-size: 14px;
   text-decoration: none;
   text-decoration: none;
+  height: 100%;
 }
 }
 </style>
 </style>

+ 24 - 0
components/Layout/Header/Title.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="d-flex flex-row">
+    <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>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useHomeUrl } from '~/composables/utils/useHomeUrl'
+import { useDisplay } from 'vuetify'
+
+const { homeUrl } = useHomeUrl()
+const { mdAndUp } = useDisplay()
+
+</script>
+
+<style scoped lang="scss">
+a {
+  color: rgb(var(--v-theme-on-primary)) !important;
+  text-decoration: none;
+}
+</style>

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

@@ -16,7 +16,7 @@
     border="solid 1px"
     border="solid 1px"
     @click="onClick"
     @click="onClick"
   >
   >
-    <v-row :no-gutters="true" style="height: 100px">
+    <v-row :no-gutters="true" :style="mdAndUp ? 'height: 100px' : ''">
       <v-col cols="3" class="flex-grow-0 flex-shrink-0 d-flex justify-center">
       <v-col cols="3" class="flex-grow-0 flex-shrink-0 d-flex justify-center">
         <v-icon
         <v-icon
           :icon="icon"
           :icon="icon"
@@ -24,12 +24,16 @@
           class="ma-2 pa-2 align-self-center text-neutral-strong"
           class="ma-2 pa-2 align-self-center text-neutral-strong"
         />
         />
       </v-col>
       </v-col>
+
       <v-col
       <v-col
         cols="9"
         cols="9"
         align-self="center"
         align-self="center"
         class="pl-2 infos-container flex-grow-1 flex-shrink-1"
         class="pl-2 infos-container flex-grow-1 flex-shrink-1"
       >
       >
-        <h4 class="text-primary">{{ $t(title) }}</h4>
+        <h4 class="text-primary">
+          {{ $t(title) }}
+        </h4>
+
         <p class="text-neutral-strong">
         <p class="text-neutral-strong">
           {{ $t(textContent) }}
           {{ $t(textContent) }}
         </p>
         </p>
@@ -43,6 +47,7 @@ import type { PropType } from '@vue/runtime-core'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
 import { useAdminUrl } from '~/composables/utils/useAdminUrl'
 import { useAdminUrl } from '~/composables/utils/useAdminUrl'
 import UrlUtils from '~/services/utils/urlUtils'
 import UrlUtils from '~/services/utils/urlUtils'
+import {useDisplay} from 'vuetify';
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -96,6 +101,8 @@ const emit = defineEmits(['click'])
 
 
 const { makeAdminUrl } = useAdminUrl()
 const { makeAdminUrl } = useAdminUrl()
 
 
+const { mdAndUp } = useDisplay()
+
 let url: string | null = null
 let url: string | null = null
 
 
 if (props.href !== null) {
 if (props.href !== null) {

+ 36 - 21
components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue

@@ -11,7 +11,7 @@
         <v-container v-if="location === 'home'">
         <v-container v-if="location === 'home'">
           <v-row>
           <v-row>
             <!-- Une personne -->
             <!-- Une personne -->
-            <v-col cols="6" v-if="ability.can('manage', 'users')">
+            <v-col cols="12" md="6" v-if="ability.can('manage', 'users')">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 @click="onCardClick('access')"
                 @click="onCardClick('access')"
                 title="a_person"
                 title="a_person"
@@ -20,7 +20,8 @@
               />
               />
             </v-col>
             </v-col>
             <v-col
             <v-col
-              cols="6"
+              cols="12"
+              md="6"
               v-if="
               v-if="
                 ability.can('display', 'agenda_page') &&
                 ability.can('display', 'agenda_page') &&
                 (ability.can('display', 'course_page') ||
                 (ability.can('display', 'course_page') ||
@@ -39,7 +40,8 @@
 
 
             <!-- Autre évènement -->
             <!-- Autre évènement -->
             <v-col
             <v-col
-              cols="6"
+              cols="12"
+              md="6"
               v-else-if="
               v-else-if="
                 ability.can('display', 'agenda_page') &&
                 ability.can('display', 'agenda_page') &&
                 ability.can('manage', 'events')
                 ability.can('manage', 'events')
@@ -57,7 +59,8 @@
 
 
             <!-- Une correspondance -->
             <!-- Une correspondance -->
             <v-col
             <v-col
-              cols="6"
+              cols="12"
+              md="6"
               v-if="
               v-if="
                 ability.can('display', 'message_send_page') &&
                 ability.can('display', 'message_send_page') &&
                 (ability.can('manage', 'emails') ||
                 (ability.can('manage', 'emails') ||
@@ -74,7 +77,11 @@
             </v-col>
             </v-col>
 
 
             <!-- Un matériel (direct link) -->
             <!-- Un matériel (direct link) -->
-            <v-col cols="6" v-if="ability.can('manage', 'equipments')">
+            <v-col
+              cols="12"
+              md="6"
+              v-if="ability.can('manage', 'equipments')"
+            >
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_materiel"
                 title="a_materiel"
                 text-content="add_any_type_material"
                 text-content="add_any_type_material"
@@ -92,7 +99,11 @@
         <v-container v-if="location === 'access'">
         <v-container v-if="location === 'access'">
           <v-row>
           <v-row>
             <!-- Un adhérent -->
             <!-- Un adhérent -->
-            <v-col cols="6" v-if="isLaw1901">
+            <v-col
+              cols="12"
+              md="6"
+              v-if="isLaw1901"
+            >
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="an_adherent"
                 title="an_adherent"
                 text-content="adherent_text_creation_card"
                 text-content="adherent_text_creation_card"
@@ -103,7 +114,11 @@
             </v-col>
             </v-col>
 
 
             <!-- Un membre du CA -->
             <!-- Un membre du CA -->
-            <v-col cols="6" v-if="isLaw1901">
+            <v-col
+              cols="12"
+              md="6"
+              v-if="isLaw1901"
+            >
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_ca_member"
                 title="a_ca_member"
                 text-content="ca_member_text_creation_card"
                 text-content="ca_member_text_creation_card"
@@ -114,7 +129,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un élève -->
             <!-- Un élève -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_student"
                 title="a_student"
                 text-content="student_text_creation_card"
                 text-content="student_text_creation_card"
@@ -125,7 +140,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un tuteur -->
             <!-- Un tuteur -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_guardian"
                 title="a_guardian"
                 text-content="guardian_text_creation_card"
                 text-content="guardian_text_creation_card"
@@ -136,7 +151,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un professeur -->
             <!-- Un professeur -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_teacher"
                 title="a_teacher"
                 text-content="teacher_text_creation_card"
                 text-content="teacher_text_creation_card"
@@ -147,7 +162,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un membre du personnel -->
             <!-- Un membre du personnel -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_member_of_staff"
                 title="a_member_of_staff"
                 text-content="personnel_text_creation_card"
                 text-content="personnel_text_creation_card"
@@ -158,7 +173,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Une entité légale -->
             <!-- Une entité légale -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_legal_entity"
                 title="a_legal_entity"
                 text-content="moral_text_creation_card"
                 text-content="moral_text_creation_card"
@@ -169,7 +184,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Une inscription en ligne -->
             <!-- Une inscription en ligne -->
-            <v-col cols="6" v-if="hasOnlineRegistrationModule">
+            <v-col cols="12" md="6" v-if="hasOnlineRegistrationModule">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="online_registration"
                 title="online_registration"
                 text-content="online_registration_text_creation_card"
                 text-content="online_registration_text_creation_card"
@@ -180,7 +195,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un autre type de contact -->
             <!-- Un autre type de contact -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="another_type_of_contact"
                 title="another_type_of_contact"
                 text-content="other_contact_text_creation_card"
                 text-content="other_contact_text_creation_card"
@@ -196,7 +211,7 @@
         <v-container v-if="location === 'event'">
         <v-container v-if="location === 'event'">
           <v-row>
           <v-row>
             <!-- Un cours -->
             <!-- Un cours -->
-            <v-col cols="6" v-if="ability.can('display', 'course_page')">
+            <v-col cols="12" md="6" v-if="ability.can('display', 'course_page')">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="course"
                 title="course"
                 text-content="course_text_creation_card"
                 text-content="course_text_creation_card"
@@ -207,7 +222,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un examen -->
             <!-- Un examen -->
-            <v-col cols="6" v-if="ability.can('display', 'exam_page')">
+            <v-col cols="12" md="6" v-if="ability.can('display', 'exam_page')">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="exam"
                 title="exam"
                 text-content="exam_text_creation_card"
                 text-content="exam_text_creation_card"
@@ -219,7 +234,7 @@
 
 
             <!-- Un projet pédagogique -->
             <!-- Un projet pédagogique -->
             <v-col
             <v-col
-              cols="6"
+              cols="12" md="6"
               v-if="ability.can('display', 'pedagogics_project_page')"
               v-if="ability.can('display', 'pedagogics_project_page')"
             >
             >
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
@@ -232,7 +247,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un autre évènement -->
             <!-- Un autre évènement -->
-            <v-col cols="6" v-if="ability.can('manage', 'events')">
+            <v-col cols="12" md="6" v-if="ability.can('manage', 'events')">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 to="event-params"
                 to="event-params"
                 href="/calendar/create/events"
                 href="/calendar/create/events"
@@ -249,7 +264,7 @@
         <v-container v-if="location === 'message'">
         <v-container v-if="location === 'message'">
           <v-row>
           <v-row>
             <!-- Un email -->
             <!-- Un email -->
-            <v-col cols="6" v-if="ability.can('manage', 'emails')">
+            <v-col cols="12" md="6" v-if="ability.can('manage', 'emails')">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="an_email"
                 title="an_email"
                 text-content="email_text_creation_card"
                 text-content="email_text_creation_card"
@@ -260,7 +275,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un courrier -->
             <!-- Un courrier -->
-            <v-col cols="6" v-if="ability.can('manage', 'mails')">
+            <v-col cols="12" md="6" v-if="ability.can('manage', 'mails')">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_letter"
                 title="a_letter"
                 text-content="letter_text_creation_card"
                 text-content="letter_text_creation_card"
@@ -271,7 +286,7 @@
             </v-col>
             </v-col>
 
 
             <!-- Un SMS -->
             <!-- Un SMS -->
-            <v-col cols="6" v-if="ability.can('manage', 'texto')">
+            <v-col cols="12" md="6" v-if="ability.can('manage', 'texto')">
               <LayoutHeaderUniversalCreationCard
               <LayoutHeaderUniversalCreationCard
                 title="a_sms"
                 title="a_sms"
                 text-content="sms_text_creation_card"
                 text-content="sms_text_creation_card"

+ 14 - 7
components/Layout/MainMenu.vue

@@ -18,6 +18,14 @@ Prend en paramètre une liste de ItemMenu et les met en forme
     </template>
     </template>
 
 
     <v-list open-strategy="single" active-class="active" class="left-menu">
     <v-list open-strategy="single" active-class="active" class="left-menu">
+      <v-list-item
+        v-if="smAndDown"
+        prepend-icon="fas fa-home"
+        :title="$t('homepage')"
+        class="theme-secondary menu-item"
+        height="48px"
+      />
+
       <!-- TODO: que se passe-t-il si le menu ne comprend qu'un seul MenuItem? -->
       <!-- TODO: que se passe-t-il si le menu ne comprend qu'un seul MenuItem? -->
       <div v-for="(item, i) in items" :key="i">
       <div v-for="(item, i) in items" :key="i">
         <!-- Cas 1 : l'item n'a pas d'enfants, c'est un lien (ou le menu est en mode réduit) -->
         <!-- Cas 1 : l'item n'a pas d'enfants, c'est un lien (ou le menu est en mode réduit) -->
@@ -37,8 +45,8 @@ Prend en paramètre une liste de ItemMenu et les met en forme
         <!-- Cas 2 : l'item a des enfants, c'est un groupe -->
         <!-- Cas 2 : l'item a des enfants, c'est un groupe -->
         <v-list-group
         <v-list-group
           v-else
           v-else
-          expand-icon="fas fa-angle-down"
-          collapse-icon="fas fa-angle-up"
+          expand-icon="fas fa-angle-right"
+          collapse-icon="fas fa-angle-down"
         >
         >
           <template #activator="{ props }">
           <template #activator="{ props }">
             <v-list-item
             <v-list-item
@@ -85,9 +93,9 @@ const organizationProfile = useOrganizationProfileStore()
 const { getMenu, hasMenu, isInternalLink, setMenuState, isMenuOpened } =
 const { getMenu, hasMenu, isInternalLink, setMenuState, isMenuOpened } =
   useMenu()
   useMenu()
 
 
-const { mdAndUp, lgAndUp } = useDisplay()
+const { smAndDown, mdAndUp, lgAndUp } = useDisplay()
 
 
-const menu = getMenu('Main')
+const menu: MenuGroup | null = getMenu('Main')
 
 
 // En vue lg+, on affiche toujours le menu
 // En vue lg+, on affiche toujours le menu
 const displayMenu = computed(() => {
 const displayMenu = computed(() => {
@@ -152,9 +160,8 @@ function getItems(
   color: rgb(var(--v-theme-on-secondary));
   color: rgb(var(--v-theme-on-secondary));
 }
 }
 
 
-.v-list-item__prepend {
-  margin: 10px 0;
-  margin-right: 10px !important;
+:deep(.v-list-item__prepend > .v-icon ~ .v-list-item__spacer) {
+  width: 12px;
 }
 }
 
 
 .v-application--is-ltr .v-list-group--no-action > .v-list-group__header {
 .v-application--is-ltr .v-list-group--no-action > .v-list-group__header {

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

@@ -7,6 +7,7 @@ A data table for the parameters page
     <div v-else>
     <div v-else>
       <LayoutParametersTable
       <LayoutParametersTable
         :items="items"
         :items="items"
+        :title="title"
         :columns-definitions="columns"
         :columns-definitions="columns"
         :actions="actions"
         :actions="actions"
         :actions-route="actionsRoute"
         :actions-route="actionsRoute"
@@ -42,6 +43,12 @@ const props = defineProps({
     type: Object as PropType<typeof ApiResource>,
     type: Object as PropType<typeof ApiResource>,
     required: true,
     required: true,
   },
   },
+  /** Titre du tableau */
+  title: {
+    type: String,
+    required: false,
+    default: null
+  },
   /**
   /**
    * If provided, define the columns to show.
    * If provided, define the columns to show.
    * Else, all the entity's props are shown.
    * Else, all the entity's props are shown.

+ 67 - 35
components/Layout/ParametersMenu.vue → components/Layout/Parameters/Menu.vue

@@ -1,16 +1,24 @@
 <template>
 <template>
   <v-navigation-drawer
   <v-navigation-drawer
-    v-if="displayMenu"
-    v-model="isOpened"
+    v-model="displayMenu"
+    :rail="isRail"
+    :disable-resize-watcher="true"
     mobile-breakpoint="sm"
     mobile-breakpoint="sm"
-    style="z-index: 1005"
+    class="parameters-menu theme-neutral-very-soft"
   >
   >
     <!--
     <!--
     Le z-index est précisé pour éviter cette erreur : https://github.com/vuetifyjs/nuxt-module/issues/205
     Le z-index est précisé pour éviter cette erreur : https://github.com/vuetifyjs/nuxt-module/issues/205
     Il pourra être retiré dès que le bug aura été corrigé
     Il pourra être retiré dès que le bug aura été corrigé
     -->
     -->
     <template #prepend>
     <template #prepend>
-      <div class="title">
+      <div v-if="!isRail" class="title">
+        <v-btn
+          flat
+          :title="$t('go_back_home')"
+          :href="homeUrl"
+          icon="fa fa-arrow-left"
+          class="mr-1"
+        />
         <h3>{{ $t('parameters') }}</h3>
         <h3>{{ $t('parameters') }}</h3>
       </div>
       </div>
     </template>
     </template>
@@ -22,21 +30,10 @@
         :title="$t(item.label)"
         :title="$t(item.label)"
         :prepend-icon="item.icon ? item.icon.name : ''"
         :prepend-icon="item.icon ? item.icon.name : ''"
         :to="(item as MenuItem).to"
         :to="(item as MenuItem).to"
+        @click="onItemClicked"
       >
       >
       </v-list-item>
       </v-list-item>
     </v-list>
     </v-list>
-
-    <template #append>
-      <v-btn
-        :href="homeUrl"
-        prepend-icon="fa fa-right-from-bracket"
-        :flat="true"
-        color="on-neutral-very-soft"
-        class="cancel-btn py-2"
-      >
-        {{ $t('exit') }}
-      </v-btn>
-    </template>
   </v-navigation-drawer>
   </v-navigation-drawer>
 </template>
 </template>
 
 
@@ -47,17 +44,34 @@ import { useMenu } from '~/composables/layout/useMenu'
 import { useHomeUrl } from '~/composables/utils/useHomeUrl'
 import { useHomeUrl } from '~/composables/utils/useHomeUrl'
 import type { MenuGroup, MenuItem } from '~/types/layout'
 import type { MenuGroup, MenuItem } from '~/types/layout'
 
 
-const { mdAndUp } = useDisplay()
+const { mdAndUp, lgAndUp } = useDisplay()
 
 
-const { getMenu, hasMenu, isMenuOpened, setMenuState } = useMenu()
+const { getMenu, hasMenu, isMenuOpened, setMenuState, closeMenu } = useMenu()
 
 
 const menu: MenuGroup | null = getMenu('Parameters')
 const menu: MenuGroup | null = getMenu('Parameters')
 
 
+const isOpened = computed(() => isMenuOpened('Parameters'))
+
+// En vue lg+, on affiche toujours le menu
 const displayMenu = computed(() => {
 const displayMenu = computed(() => {
-  return menu !== null && hasMenu('Parameters')
+  return menu !== null && hasMenu('Parameters') && (lgAndUp.value || isOpened.value)
 })
 })
 
 
-const isOpened = computed(() => isMenuOpened('Parameters'))
+// 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
+  )
+})
+
+const onItemClicked = () => {
+  if (!lgAndUp.value) {
+    closeMenu('Parameters')
+  }
+}
 
 
 const unwatch = watch(mdAndUp, () => {
 const unwatch = watch(mdAndUp, () => {
   // Par défaut si l'écran est trop petit au chargement de la page, le menu doit rester fermé.
   // Par défaut si l'écran est trop petit au chargement de la page, le menu doit rester fermé.
@@ -71,11 +85,36 @@ const { homeUrl } = useHomeUrl()
 onUnmounted(() => {
 onUnmounted(() => {
   unwatch()
   unwatch()
 })
 })
+
+// TODO voir à factoriser avec LayoutMainMenu
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
+
+.parameters-menu {
+  position: relative;
+  overflow: hidden;
+}
+
+.parameters-menu::before {
+  content: "\f013";
+  font-family: "Font Awesome 6 Free",serif;
+  font-weight: 900;
+  font-size: 300px;
+  color: rgb(var(--v-theme-neutral-soft));
+  position: absolute;
+  top: 90%;
+  left: 12%;
+  transform: translate(-50%, -50%);
+  pointer-events: none;
+  user-select: none;
+}
+
+
 .title {
 .title {
   display: flex;
   display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
   align-items: center;
   align-items: center;
   height: 48px;
   height: 48px;
   vertical-align: center;
   vertical-align: center;
@@ -116,25 +155,18 @@ onUnmounted(() => {
 :deep(.v-list-item__prepend) {
 :deep(.v-list-item__prepend) {
   margin: 10px 0;
   margin: 10px 0;
   margin-right: 10px !important;
   margin-right: 10px !important;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  width: 32px;
+
+  .v-list-item__spacer {
+    display: none;
+  }
 }
 }
 
 
 :deep(.v-list-item .v-icon) {
 :deep(.v-list-item .v-icon) {
   max-width: 24px;
   max-width: 24px;
   margin-right: 10px;
   margin-right: 10px;
 }
 }
-
-.cancel-btn {
-  height: 42px;
-  color: rgb(var(--v-theme-on-neutral-very-soft));
-  background-color: transparent;
-  width: 100%;
-  border-top: solid 1px rgb(var(--v-theme-on-neutral-very-soft));
-  display: flex;
-  flex-direction: row;
-  justify-content: flex-start;
-}
-
-:deep(.cancel-btn .v-btn__prepend) {
-  margin: 0 16px 4px 2px;
-}
 </style>
 </style>

+ 19 - 0
components/Layout/Parameters/Section.vue

@@ -0,0 +1,19 @@
+<template>
+  <v-card class="parameters-page-card">
+    <slot />
+  </v-card>
+</template>
+
+<style scoped lang="scss">
+.parameters-page-card {
+  background-color: rgb(var(--v-theme-neutral-very-soft));
+  color: rgb(var(--v-theme-on-neutral-very-soft));
+  padding: 24px;
+  margin: 28px auto;
+  max-width: 1200px;
+
+  @media (max-width: 600px) {
+    max-width: 95%;
+  }
+}
+</style>

+ 108 - 42
components/Layout/Parameters/Table.vue

@@ -3,42 +3,76 @@ A data table for the parameters page
 -->
 -->
 <template>
 <template>
   <div class="container">
   <div class="container">
+    <div class="d-flex flex-row mb-2">
+      <h4 v-if="title" class="align-self-center">
+        {{ title }}
+      </h4>
+    </div>
+
     <v-table>
     <v-table>
       <thead>
       <thead>
         <tr>
         <tr>
           <td v-for="col in columns">
           <td v-for="col in columns">
             {{ col.label }}
             {{ col.label }}
           </td>
           </td>
-          <td>{{ i18n.t('actions') }}</td>
+          <td>{{ $t('actions') }}</td>
         </tr>
         </tr>
       </thead>
       </thead>
-      <tbody v-if="items">
-        <tr v-for="(item, i) in items" :key="i">
-          <td v-for="col in columnsDefinitions" class="cycle-editable-cell">
+      <tbody v-if="items.length > 0">
+        <tr
+          v-for="(item, i) in items"
+          :key="i"
+        >
+          <td
+            v-for="col in columnsDefinitions"
+            class="cycle-editable-cell"
+          >
             {{ item[col.property] }}
             {{ item[col.property] }}
           </td>
           </td>
 
 
-          <td class="d-flex flex-row actions-cell">
-            <slot name="actions" :item="item">
-              <v-btn
-                v-if="actions.includes(TABLE_ACTION.EDIT)"
-                :flat="true"
-                icon="fa fa-pen"
-                class="mr-3"
-                @click="emit('editClicked', item)"
-              />
-              <v-btn
-                v-if="actions.includes(TABLE_ACTION.DELETE)"
-                :flat="true"
-                icon="fas fa-trash"
-                @click="emit('deleteClicked', item)"
-              />
-            </slot>
+          <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-btn
+                  v-if="actions.includes(TABLE_ACTION.EDIT) || actions.includes(TABLE_ACTION.DELETE)"
+                  v-bind="props"
+                  :flat="true"
+                  icon="fas fa-ellipsis-vertical"
+                />
+              </template>
+
+              <v-list>
+                <v-list-item
+                  v-if="actions.includes(TABLE_ACTION.EDIT)"
+                  @click="emit('editClicked', item)"
+                >
+                  <v-list-item-title>
+                    <v-icon>fas fa-pen</v-icon>
+                    {{ $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"
+                >
+                  <v-list-item-title icon="fas fa-trash">
+                    <v-icon>fas fa-trash</v-icon>
+                    {{ $t("delete") }}
+                  </v-list-item-title>
+                </v-list-item>
+              </v-list>
+            </v-menu>
           </td>
           </td>
         </tr>
         </tr>
       </tbody>
       </tbody>
       <tbody v-else>
       <tbody v-else>
-        <tr class="theme-neutral">
+        <tr>
           <td>
           <td>
             <i>{{ i18n.t('nothing_to_show') }}</i>
             <i>{{ i18n.t('nothing_to_show') }}</i>
           </td>
           </td>
@@ -46,11 +80,11 @@ A data table for the parameters page
         </tr>
         </tr>
       </tbody>
       </tbody>
     </v-table>
     </v-table>
-    <div class="d-flex justify-end" v-if="actions.includes(TABLE_ACTION.ADD)">
+
+    <div v-if="actions.includes(TABLE_ACTION.ADD)" class="d-flex justify-center my-3">
       <v-btn
       <v-btn
-        :flat="true"
         prepend-icon="fa fa-plus"
         prepend-icon="fa fa-plus"
-        class="theme-primary mt-4"
+        class="theme-neutral"
         @click="emit('addClicked')"
         @click="emit('addClicked')"
       >
       >
         {{ i18n.t('add') }}
         {{ i18n.t('add') }}
@@ -60,9 +94,9 @@ A data table for the parameters page
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { TABLE_ACTION } from '~/types/enum/enums'
-import UrlUtils from '~/services/utils/urlUtils'
-import type { ColumnDefinition } from '~/types/interfaces'
+import {TABLE_ACTION} from '~/types/enum/enums';
+import type {ColumnDefinition} from '~/types/interfaces';
+import {useDisplay} from 'vuetify';
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -72,6 +106,12 @@ const props = defineProps({
     type: Array as PropType<Array<object>>,
     type: Array as PropType<Array<object>>,
     required: true,
     required: true,
   },
   },
+  /** Titre du tableau */
+  title: {
+    type: String,
+    required: false,
+    default: null
+  },
   /**
   /**
    * If provided, define the columns to show.
    * If provided, define the columns to show.
    * Else, all the entity's props are shown.
    * Else, all the entity's props are shown.
@@ -84,7 +124,7 @@ const props = defineProps({
   columnsDefinitions: {
   columnsDefinitions: {
     type: Array as PropType<Array<ColumnDefinition> | null>,
     type: Array as PropType<Array<ColumnDefinition> | null>,
     required: false,
     required: false,
-    default: null,
+    default: null
   },
   },
   /**
   /**
    * The property used as identifier (required by 'edition' link)
    * The property used as identifier (required by 'edition' link)
@@ -92,7 +132,7 @@ const props = defineProps({
   identifier: {
   identifier: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: 'id',
+    default: 'id'
   },
   },
   /**
   /**
    * List of the actions available for each record
    * List of the actions available for each record
@@ -100,7 +140,7 @@ const props = defineProps({
   actions: {
   actions: {
     type: Array as PropType<Array<TABLE_ACTION>>,
     type: Array as PropType<Array<TABLE_ACTION>>,
     required: false,
     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
    * The URL for the edit / create pages
@@ -112,43 +152,47 @@ const props = defineProps({
   actionsRoute: {
   actionsRoute: {
     type: String,
     type: String,
     required: false,
     required: false,
-    default: '/parameters',
-  },
+    default: '/parameters'
+  }
 })
 })
 
 
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const emit = defineEmits(['editClicked', 'deleteClicked', 'addClicked'])
 const emit = defineEmits(['editClicked', 'deleteClicked', 'addClicked'])
 
 
+const { smAndUp, xs } = useDisplay()
+
 const getId = (item: object) => {
 const getId = (item: object) => {
   return item[props.identifier]
   return item[props.identifier]
 }
 }
 
 
 const columns: ComputedRef<Array<ColumnDefinition>> = computed(() => {
 const columns: ComputedRef<Array<ColumnDefinition>> = computed(() => {
-  return props.columnsDefinitions.map((col) => {
+  return props.columnsDefinitions.map(col => {
     return {
     return {
       property: col.property,
       property: col.property,
-      label: col.label ?? i18n.t(col.property),
+      label: col.label ?? i18n.t(col.property)
     }
     }
   })
   })
 })
 })
+
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
+
 .container {
 .container {
-  max-width: 1000px;
+  //max-width: 1000px;
+  //margin: 0 auto;
+  display: inline-block;
+  min-width: 65%;
 }
 }
 
 
 .v-table {
 .v-table {
-  width: 100%;
-
   thead {
   thead {
     color: rgb(var(--v-theme-neutral-strong));
     color: rgb(var(--v-theme-neutral-strong));
     font-weight: 600;
     font-weight: 600;
 
 
     td {
     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 {
     td:last-of-type {
@@ -156,8 +200,14 @@ const columns: ComputedRef<Array<ColumnDefinition>> = computed(() => {
     }
     }
   }
   }
 
 
-  th,
-  td {
+  :deep(tr:hover) {
+    .fa-ellipsis-vertical {
+      color: rgb(var(--v-theme-on-neutral-soft));
+      font-size: 20px;
+    }
+  }
+
+  th, td {
     padding: 10px;
     padding: 10px;
     text-align: left;
     text-align: left;
   }
   }
@@ -167,8 +217,24 @@ const columns: ComputedRef<Array<ColumnDefinition>> = computed(() => {
   }
   }
 }
 }
 
 
+.action-menu {
+  margin: 0 auto;
+
+  .v-list {
+    top: 26px;
+    left: 2px;
+    padding: 0;
+  }
+
+  .v-icon {
+    opacity: 0.7;
+    font-size: 16px;
+    margin-right: 12px;
+  }
+}
+
 :deep(.actions-cell .v-icon) {
 :deep(.actions-cell .v-icon) {
-  color: rgb(var(--v-theme-neutral-strong));
+  color: rgb(var(--v-theme-on-neutral));
   font-size: 18px;
   font-size: 18px;
 }
 }
 </style>
 </style>

+ 93 - 0
components/Layout/Parameters/Website/ActivationSwitch.vue

@@ -0,0 +1,93 @@
+<template>
+  <div>
+    <v-switch
+      :model-value="modelValue"
+      :label="$t('activateOpentalentSiteWeb')"
+      inset
+      :color="color"
+      :base-color="color"
+      false-icon="fas fa-xmark"
+      true-icon="fas fa-check"
+      hide-details
+      @update:modelValue="onUpdate"
+    />
+
+    <LazyLayoutDialog :show="showWebsiteDeactivationDialog" theme="warning">
+      <template #dialogTitle>
+        {{ $t('please_confirm') }}
+      </template>
+      <template #dialogText>
+        <v-col>
+          <div>
+            {{
+              $t(
+                'yourOpentalentWebsiteWillBeDeactivatedOnceYouLlHaveSaved',
+              )
+            }}.
+          </div>
+          <span>{{ $t('doYouWantToContinue') }} ?</span>
+        </v-col>
+      </template>
+      <template #dialogBtn>
+        <v-btn
+          class="theme-neutral-soft mr-4"
+          @click="onDialogNoBtnClick"
+        >
+          {{ $t('cancel') }}
+        </v-btn>
+
+        <v-btn class="theme-primary" @click="onDialogYesBtnClick">
+          {{ $t('yes') }}
+        </v-btn>
+      </template>
+    </LazyLayoutDialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+
+import {useTheme} from 'vuetify';
+import type {Ref} from 'vue';
+
+const theme = useTheme()
+
+const emit = defineEmits(['update:modelValue'])
+
+const i18n = useI18n()
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    required: true,
+  }
+})
+
+const color = computed(() => props.modelValue ?
+  theme.current.value.colors['success'] :
+  theme.current.value.colors['danger']
+)
+
+const showWebsiteDeactivationDialog: Ref<boolean> = ref(false)
+
+const onUpdate = (value: boolean) => {
+  if (!value) {
+    showWebsiteDeactivationDialog.value = true
+  } else {
+    emit('update:modelValue', value)
+  }
+}
+
+const onDialogYesBtnClick = () => {
+  showWebsiteDeactivationDialog.value = false
+  emit('update:modelValue', false)
+}
+
+const onDialogNoBtnClick = () => {
+  showWebsiteDeactivationDialog.value = false
+  props.modelValue = true
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

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

@@ -93,7 +93,7 @@ const setActivityYear = async (event: string) => {
   input {
   input {
     font-size: 14px;
     font-size: 14px;
     width: 55px !important;
     width: 55px !important;
-    padding: 0 !important;
+    padding: 0 2px 0 6px !important;
     margin-top: 0 !important;
     margin-top: 0 !important;
     min-height: 24px;
     min-height: 24px;
     height: 24px;
     height: 24px;

+ 7 - 1
components/Layout/SubHeader/DataTimingRange.vue

@@ -5,9 +5,11 @@
         {{ $t('period_choose') }}
         {{ $t('period_choose') }}
       </span>
       </span>
 
 
-      <UiDateRangePicker
+      <UiDatePicker
         :model-value="datesRange"
         :model-value="datesRange"
         :max-height="28"
         :max-height="28"
+        :range="true"
+        :autoApply="false"
         @update:model-value="updateDateTimeRange"
         @update:model-value="updateDateTimeRange"
       />
       />
     </div>
     </div>
@@ -73,4 +75,8 @@ const updateDateTimeRange = async (dates: Array<Date>): Promise<any> => {
 .label {
 .label {
   min-width: 150px;
   min-width: 150px;
 }
 }
+
+:deep(.dp__input) {
+  max-height: 28px;
+}
 </style>
 </style>

+ 1 - 0
components/Layout/Subheader.vue

@@ -101,6 +101,7 @@ main {
 }
 }
 
 
 :deep(#subheader .v-card) {
 :deep(#subheader .v-card) {
+  min-height: 36px;
   max-height: 36px;
   max-height: 36px;
   background-color: transparent !important;
   background-color: transparent !important;
 }
 }

+ 10 - 1
components/Ui/Button/Submit.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
   <v-btn
   <v-btn
-    class="mr-4 theme-primary"
+    class="submit-btn theme-primary"
     :class="hasOtherActions ? 'pr-0' : ''"
     :class="hasOtherActions ? 'pr-0' : ''"
     @click="submitAction(mainAction)"
     @click="submitAction(mainAction)"
     ref="mainBtn"
     ref="mainBtn"
@@ -88,6 +88,15 @@ const hasOtherActions: ComputedRef<boolean> = computed(() => {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
+
+.submit-btn {
+  margin-right: 12px;
+
+  @media (max-width: 600px) {
+    margin: 0;
+  }
+}
+
 .v-list-item--dense {
 .v-list-item--dense {
   min-height: 25px;
   min-height: 25px;
 }
 }

+ 65 - 92
components/Ui/DatePicker.vue

@@ -1,119 +1,92 @@
 <!--
 <!--
-Sélecteur de dates avec Vuetify
+Sélecteur de dates
 
 
-@see https://vuetifyjs.com/en/components/date-pickers/
+@see https://vue3datepicker.com/
 -->
 -->
 
 
 <template>
 <template>
-  <v-layout row wrap>
-    <!--
-    TODO: remplacer par <v-date-input> quand celui ci ne sera plus expérimental
-    (@see https://vuetifyjs.com/en/components/date-inputs)
-    -->
-    <v-menu
-      v-model="menu"
-      :close-on-content-click="false"
-      :nudge-right="40"
-      lazy
-      transition="scale-transition"
-      offset-y
-      :max-width="290"
-      :min-width="290"
-      :position-x="positionX"
-      :position-y="positionY"
-    >
-      <template #activator="{ props: attrs }">
-        <v-text-field
-          v-model="displayDate"
-          :label="label"
-          :readonly="true"
-          v-bind="attrs"
-          prepend-inner-icon="far fa-calendar"
-          variant="outlined"
-          density="compact"
-        />
-      </template>
-
-      <v-date-picker
+  <div class="date-picker">
+    <VueDatePicker
         :model-value="modelValue"
         :model-value="modelValue"
-        :locale="i18n.locale.value"
-        no-title
-        scrollable
-        @update:model-value="updateDate"
+        :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"
       />
       />
-    </v-menu>
-  </v-layout>
+  </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref, computed, nextTick, watch, type PropType } from 'vue'
 import { useI18n } from 'vue-i18n'
 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';
 
 
 const props = defineProps({
 const props = defineProps({
-  modelValue: Date,
-  label: {
-    type: String,
-    default: '',
-  },
-  format: {
-    type: String,
+  modelValue: {
+    type: Object as PropType<Date | Array<Date> | null>,
+    required: false,
     default: null,
     default: null,
   },
   },
-  /**
-   * Position du date-picker
-   * @see https://vuetifyjs.com/en/api/v-menu/#props-position
-   */
-  position: {
-    type: String as PropType<'left' | 'center' | 'right'>,
-    default: 'center',
+  range: {
+    type: Boolean,
+    default: false,
+  },
+  withTimePicker: {
+    type: Boolean,
+    default: false,
+  },
+  autoApply: {
+    type: Boolean,
+    default: true,
   },
   },
+  readonly: {
+    type: Boolean,
+    default: false,
+  }
 })
 })
 
 
-const emit = defineEmits(['update:modelValue'])
-
 const i18n = useI18n()
 const i18n = useI18n()
-const menu = ref(false)
-const positionX = ref(0)
-const positionY = ref(0)
 
 
-const displayDate = computed({
-  get: () => {
-    if (!props.modelValue) return ''
-    if (props.format) {
-      return new Intl.DateTimeFormat(i18n.locale.value, {
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit',
-      }).format(props.modelValue)
-    }
-    return props.modelValue.toLocaleDateString(i18n.locale.value)
-  },
-  set: () => {},
-})
+const locale: Ref<string> = i18n.locale
 
 
-function updateDate(value: Date) {
-  emit('update:modelValue', value)
-  menu.value = false
-}
+const fnsLocale: ComputedRef<Locale> = computed(
+  () => DateUtils.getFnsLocale(locale.value as supportedLocales)
+)
 
 
-function updatePosition() {
-  nextTick(() => {
-    const activator = document.querySelector('.v-menu__activator')
-    if (activator) {
-      const rect = activator.getBoundingClientRect()
-      positionX.value = rect.left
-      positionY.value = rect.bottom
-    }
-  })
-}
+const dateFormatPattern: ComputedRef<string> = computed(
+  () => DateUtils.getShortFormatPattern(locale.value as supportedLocales)
+)
 
 
-watch(menu, (val) => {
-  if (val) updatePosition()
-})
+const today = new Date()
 </script>
 </script>
 
 
-<style scoped>
-.v-menu__content {
-  position: absolute !important;
+<style lang="scss">
+:deep(.dp__active_date) {
+  border-radius: 12px;
+}
+
+:deep(.dp__today) {
+  border-radius: 12px;
+  border: 1px solid rgb(var(--v-theme-neutral-strong)) !important;
+}
+
+:deep(.dp__action_button) {
+  height: 32px;
 }
 }
 </style>
 </style>

+ 0 - 129
components/Ui/DateRangePicker.vue

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

+ 18 - 2
components/Ui/Form.vue

@@ -34,7 +34,7 @@ de quitter si des données ont été modifiées.
           </v-col>
           </v-col>
         </v-row>
         </v-row>
       </v-container>
       </v-container>
-      <div v-else class="mt-12" />
+      <div v-else class="mt-6" />
 
 
       <!-- Content -->
       <!-- Content -->
       <slot v-bind="{ modelValue }" />
       <slot v-bind="{ modelValue }" />
@@ -43,7 +43,7 @@ de quitter si des données ont été modifiées.
       <v-container
       <v-container
         v-if="actionPosition === 'both' || actionPosition === 'bottom'"
         v-if="actionPosition === 'both' || actionPosition === 'bottom'"
         :fluid="true"
         :fluid="true"
-        class="container btnActions mt-6"
+        class="container btnActions"
       >
       >
         <v-row>
         <v-row>
           <v-col cols="12" sm="12">
           <v-col cols="12" sm="12">
@@ -410,6 +410,22 @@ defineExpose({ validate })
 <style scoped>
 <style scoped>
 .btnActions {
 .btnActions {
   text-align: right;
   text-align: right;
+
+  @media (max-width: 600px) {
+    :deep(.v-col-12) {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      width: 100%;
+
+      .v-btn {
+        margin: 12px 0 !important;
+        max-width: 250px;
+      }
+    }
+  }
+
 }
 }
 
 
 .confirmation-dlg-actions {
 .confirmation-dlg-actions {

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

@@ -1,8 +1,8 @@
 <template>
 <template>
-  <LazyLayoutDialog :show="modelValue">
-    <template #dialogType>
-      {{ $t('delete_assistant') }}
-    </template>
+  <LazyLayoutDialog
+    :show="modelValue"
+    theme="danger"
+  >
 
 
     <template #dialogTitle>
     <template #dialogTitle>
       {{ $t('caution') }}
       {{ $t('caution') }}
@@ -15,10 +15,7 @@
     </template>
     </template>
 
 
     <template #dialogBtn>
     <template #dialogBtn>
-      <v-btn
-        class="mr-4 submitBtn theme-neutral-strong"
-        @click="onCancelClicked"
-      >
+      <v-btn class="mr-4 submitBtn theme-neutral" @click="onCancelClicked">
         {{ $t('cancel') }}
         {{ $t('cancel') }}
       </v-btn>
       </v-btn>
       <v-btn class="mr-4 submitBtn theme-danger" @click="onDeleteClicked">
       <v-btn class="mr-4 submitBtn theme-danger" @click="onDeleteClicked">
@@ -31,15 +28,11 @@
 <script setup lang="ts">
 <script setup lang="ts">
 const props = defineProps({
 const props = defineProps({
   modelValue: {
   modelValue: {
-    type: Boolean,
-  },
+    type: Boolean
+  }
 })
 })
 
 
-const emit = defineEmits([
-  'cancelClicked',
-  'deleteClicked',
-  'update:modelValue',
-])
+const emit = defineEmits(['cancelClicked', 'deleteClicked', 'update:modelValue'])
 
 
 const onCancelClicked = () => {
 const onCancelClicked = () => {
   emit('cancelClicked')
   emit('cancelClicked')
@@ -52,4 +45,6 @@ const onDeleteClicked = () => {
 }
 }
 </script>
 </script>
 
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+
+</style>

+ 2 - 0
components/Ui/Image.vue

@@ -95,6 +95,8 @@ const {
   refresh: refreshImage,
   refresh: refreshImage,
 } = (await fetch(fileId, defaultImagePath, props.height, props.width)) as any
 } = (await fetch(fileId, defaultImagePath, props.height, props.width)) as any
 
 
+console.log(imageSrc.value)
+
 const refresh = () => {
 const refresh = () => {
   refreshImage()
   refreshImage()
 }
 }

+ 18 - 0
components/Ui/Input/Autocomplete.vue

@@ -27,15 +27,18 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
       "
       "
       :rules="rules"
       :rules="rules"
       :chips="chips"
       :chips="chips"
+      :closable-chips="closableChips"
       :hide-no-data="hideNoData"
       :hide-no-data="hideNoData"
       :no-data-text="
       :no-data-text="
         isLoading ? $t('please_wait') : $t('no_result_matching_your_request')
         isLoading ? $t('please_wait') : $t('no_result_matching_your_request')
       "
       "
       :variant="variant"
       :variant="variant"
+      density="compact"
       @update:model-value="onUpdate"
       @update:model-value="onUpdate"
       @update:search="emit('update:search', $event)"
       @update:search="emit('update:search', $event)"
       @update:menu="emit('update:menu', $event)"
       @update:menu="emit('update:menu', $event)"
       @update:focused="emit('update:focused', $event)"
       @update:focused="emit('update:focused', $event)"
+      class="mb-3"
     >
     >
       <template v-if="slotText" #item="data">
       <template v-if="slotText" #item="data">
         <!--        <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>-->
         <!--        <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>-->
@@ -144,6 +147,14 @@ const props = defineProps({
     type: Boolean,
     type: Boolean,
     default: false,
     default: false,
   },
   },
+  /**
+   * Permet de retirer une puce directement
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-closable-chips
+   */
+  closableChips: {
+    type: Boolean,
+    default: false,
+  },
   /**
   /**
    * Le contenu de la liste est en cours de chargement
    * Le contenu de la liste est en cours de chargement
    */
    */
@@ -359,3 +370,10 @@ const prepareItem = (item: Object): AnyJson => {
   })
   })
 }
 }
 </script>
 </script>
+
+<style scoped lang="scss">
+:deep(.v-chip__close .v-icon) {
+  font-size: 16px;
+  color: rgb(var(--v-theme-on-neutral));
+}
+</style>

+ 13 - 0
components/Ui/Input/Autocomplete/Accesses.vue

@@ -16,12 +16,14 @@ Champs autocomplete dédié à la recherche des Accesses d'une structure
       :multiple="multiple"
       :multiple="multiple"
       hide-no-data
       hide-no-data
       :chips="chips"
       :chips="chips"
+      :closable-chips="true"
       :auto-select-first="false"
       :auto-select-first="false"
       prepend-inner-icon="fas fa-magnifying-glass"
       prepend-inner-icon="fas fa-magnifying-glass"
       :return-object="false"
       :return-object="false"
       :variant="variant"
       :variant="variant"
       @update:model-value="onUpdateModelValue"
       @update:model-value="onUpdateModelValue"
       @update:search="onUpdateSearch"
       @update:search="onUpdateSearch"
+      :class="(pending || pageStore.loading) ? 'hide-selection' : ''"
     />
     />
   </main>
   </main>
 </template>
 </template>
@@ -139,6 +141,7 @@ interface UserListItem {
 
 
 const { fetchCollection } = useEntityFetch()
 const { fetchCollection } = useEntityFetch()
 const i18n = useI18n()
 const i18n = useI18n()
+const pageStore = usePageStore()
 
 
 /**
 /**
  * Génère un AccessListItem à partir d'un Access
  * Génère un AccessListItem à partir d'un Access
@@ -257,4 +260,14 @@ const onUpdateModelValue = (event: Array<number>) => {
 .v-autocomplete {
 .v-autocomplete {
   min-width: 350px;
   min-width: 350px;
 }
 }
+
+.hide-selection {
+  /**
+      On cache le contenu au chargement en attendant de résoudre le bug qui fait
+      que ce sont les ids ou les IRIs qui s'affichent le temps du chargement
+   */
+  :deep(.v-chip__content) {
+    color: transparent !important;
+  }
+}
 </style>
 </style>

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

@@ -9,9 +9,10 @@ Case à cocher, à placer dans un composant `UiForm`
     :model-value="modelValue"
     :model-value="modelValue"
     :label="$t(fieldLabel)"
     :label="$t(fieldLabel)"
     :disabled="readonly"
     :disabled="readonly"
+    density="compact"
     :error="error || !!fieldViolations"
     :error="error || !!fieldViolations"
     :error-messages="errorMessage || fieldViolations ? $t(fieldViolations) : ''"
     :error-messages="errorMessage || fieldViolations ? $t(fieldViolations) : ''"
-    class="py-1"
+    class="checkbox py-1"
     @update:model-value="onUpdate"
     @update:model-value="onUpdate"
   />
   />
 </template>
 </template>
@@ -92,4 +93,21 @@ const onUpdate = (event: boolean) => {
 }
 }
 </script>
 </script>
 
 
-<style scoped></style>
+<style scoped lang="scss">
+
+.checkbox {
+  margin-top: -4px;
+  margin-bottom: 8px;
+}
+
+@media (min-width: 600px) {
+  :deep(.v-input__control) {
+    max-height: 32px;
+  }
+}
+
+:deep(.v-label) {
+  padding-left: 8px;
+}
+
+</style>

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

@@ -12,6 +12,7 @@ Liste déroulante, à placer dans un composant `UiForm`
       :label="$t(fieldLabel)"
       :label="$t(fieldLabel)"
       :items="items"
       :items="items"
       :disabled="readonly"
       :disabled="readonly"
+      density="compact"
       :error="error || !!fieldViolations"
       :error="error || !!fieldViolations"
       :error-messages="
       :error-messages="
         errorMessage || fieldViolations ? $t(fieldViolations) : ''
         errorMessage || fieldViolations ? $t(fieldViolations) : ''

+ 49 - 23
components/Ui/Input/DatePicker.vue

@@ -4,14 +4,15 @@ Sélecteur de dates, à placer dans un composant `UiForm`
 
 
 <template>
 <template>
   <main>
   <main>
-    <div class="d-flex flex-column">
-      <span>{{ $t(fieldLabel) }}</span>
+    <div class="d-flex flex-column container">
+      <span class="label">
+        {{ $t(fieldLabel) }}
+      </span>
 
 
       <UiDatePicker
       <UiDatePicker
-        v-model="date"
+        :model-value="date"
         :readonly="readonly"
         :readonly="readonly"
-        :format="format"
-        :position="position"
+        class="date-picker"
         @update:model-value="onUpdate($event)"
         @update:model-value="onUpdate($event)"
       />
       />
 
 
@@ -63,15 +64,6 @@ const props = defineProps({
     type: Boolean,
     type: Boolean,
     required: false,
     required: false,
   },
   },
-  /**
-   * Format d'affichage des dates
-   * @see https://vue3datepicker.com/props/formatting/
-   */
-  format: {
-    type: String,
-    required: false,
-    default: null,
-  },
   /**
   /**
    * Règles de validation
    * Règles de validation
    * @see https://vuetify.cn/en/components/forms/#validation-with-submit-clear
    * @see https://vuetify.cn/en/components/forms/#validation-with-submit-clear
@@ -96,14 +88,6 @@ const props = defineProps({
     required: false,
     required: false,
     default: null,
     default: null,
   },
   },
-  /**
-   * @see https://vue3datepicker.com/props/positioning/#position
-   */
-  position: {
-    type: String as PropType<'left' | 'center' | 'right'>,
-    required: false,
-    default: 'center',
-  },
 })
 })
 
 
 const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
 const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
@@ -117,9 +101,51 @@ const date: Ref<Date | undefined> = ref(
 )
 )
 
 
 const onUpdate = (event: string) => {
 const onUpdate = (event: string) => {
+  console.log(event)
   updateViolationState(event)
   updateViolationState(event)
+  date.value = event ? new Date(event) : undefined
   emit('update:model-value', date.value ? formatISO(date.value) : undefined)
   emit('update:model-value', date.value ? formatISO(date.value) : undefined)
 }
 }
 </script>
 </script>
 
 
-<style scoped></style>
+<style scoped lang="scss">
+
+.container {
+  position: relative;
+}
+
+.label {
+  position: absolute;
+  color: #8e8e8e;
+  top: -0.7rem;
+  left: 0.75rem;
+  background-color: rgb(var(--v-theme-surface));
+  padding: 0 0.3rem;
+  font-size: 0.8rem;
+  z-index: 1;
+  transition: color 0.2s, font-size 0.2s, top 0.2s;
+
+}
+
+.date-picker:hover {
+  :deep(.dp__input) {
+    border-color: #333333;
+  }
+}
+
+.container:focus-within {
+  .label {
+    color: #333333;
+  }
+
+  :deep(.dp__input_focus) {
+    border: solid 2px #333333;
+  }
+
+  :deep(.dp__input_icon) {
+    color: #333333;
+  }
+}
+
+
+</style>

+ 4 - 1
components/Ui/Input/Email.vue

@@ -19,6 +19,7 @@ import { useNuxtApp } from '#app'
 import { useFieldViolation } from '~/composables/form/useFieldViolation'
 import { useFieldViolation } from '~/composables/form/useFieldViolation'
 import { useValidationUtils } from '~/composables/utils/useValidationUtils'
 import { useValidationUtils } from '~/composables/utils/useValidationUtils'
 import type { PropType } from '@vue/runtime-core'
 import type { PropType } from '@vue/runtime-core'
+import { useI18n } from 'vue-i18n'
 
 
 const props = defineProps({
 const props = defineProps({
   label: {
   label: {
@@ -74,7 +75,9 @@ const props = defineProps({
   },
   },
 })
 })
 
 
-const { emit, i18n } = useNuxtApp()
+const { emit } = useNuxtApp()
+
+const i18n = useI18n()
 
 
 const fieldLabel = props.label ?? props.field
 const fieldLabel = props.label ?? props.field
 
 

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

@@ -22,6 +22,7 @@ Liste déroulante dédiée à l'affichage d'objets Enum
       :error-messages="
       :error-messages="
         errorMessage || (fieldViolations ? $t(fieldViolations) : '')
         errorMessage || (fieldViolations ? $t(fieldViolations) : '')
       "
       "
+      density="compact"
       @update:modelValue="
       @update:modelValue="
         updateViolationState($event)
         updateViolationState($event)
         $emit('update:modelValue', $event)
         $emit('update:modelValue', $event)

+ 16 - 2
components/Ui/Input/Image.vue

@@ -34,6 +34,7 @@ Assistant de création d'image
           <div v-else>
           <div v-else>
             <div class="upload__cropper-wrapper">
             <div class="upload__cropper-wrapper">
               <Cropper
               <Cropper
+                v-if="croppingEnabled"
                 ref="cropper"
                 ref="cropper"
                 class="upload__cropper"
                 class="upload__cropper"
                 check-orientation
                 check-orientation
@@ -43,6 +44,12 @@ Assistant de création d'image
                 @change="onCropperChange"
                 @change="onCropperChange"
               />
               />
 
 
+              <v-img
+                v-else
+                :src="currentImage.src ?? ''"
+                class="upload__cropper"
+              />
+
               <div
               <div
                 v-if="currentImage.src"
                 v-if="currentImage.src"
                 class="upload__reset-button"
                 class="upload__reset-button"
@@ -107,8 +114,7 @@ const props = defineProps({
     default: null,
     default: null,
   },
   },
   /**
   /**
-   * Label du champ
-   * Si non défini, c'est le nom de propriété qui est utilisé
+   * Nom du champ
    */
    */
   field: {
   field: {
     type: String,
     type: String,
@@ -136,6 +142,14 @@ const props = defineProps({
     type: Number,
     type: Number,
     required: false,
     required: false,
   },
   },
+  /**
+   * Donne la possibilité de rogner les images
+   */
+  croppingEnabled: {
+    type: Boolean,
+    required: false,
+    default: true,
+  },
   /**
   /**
    * TODO: completer
    * TODO: completer
    */
    */

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

@@ -9,9 +9,9 @@ An input for numeric values
     :model-value.number="modelValue"
     :model-value.number="modelValue"
     :label="label || field ? $t(label ?? field) : undefined"
     :label="label || field ? $t(label ?? field) : undefined"
     hide-details
     hide-details
-    :density="density"
     type="number"
     type="number"
     :variant="variant"
     :variant="variant"
+    density="compact"
     @update:model-value="onModelUpdate($event)"
     @update:model-value="onModelUpdate($event)"
   />
   />
 </template>
 </template>
@@ -60,11 +60,6 @@ const props = defineProps({
     required: false,
     required: false,
     default: null,
     default: null,
   },
   },
-  density: {
-    type: String as PropType<Density>,
-    required: false,
-    default: 'default',
-  },
   /**
   /**
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
    */
    */

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

@@ -18,6 +18,7 @@ Champs de saisie de texte, à placer dans un composant `UiForm`
     "
     "
     :append-icon="type === 'password' ? (show ? 'mdi-eye' : 'mdi-eye-off') : ''"
     :append-icon="type === 'password' ? (show ? 'mdi-eye' : 'mdi-eye-off') : ''"
     :variant="variant"
     :variant="variant"
+    density="compact"
     @click:append="show = !show"
     @click:append="show = !show"
     @update:model-value="onUpdate($event)"
     @update:model-value="onUpdate($event)"
     @change="onChange($event)"
     @change="onChange($event)"

+ 15 - 26
components/Ui/SystemBar.vue

@@ -3,23 +3,20 @@ System bars
 -->
 -->
 
 
 <template>
 <template>
-  <v-system-bar
-    height="50"
-    :class="
-      'd-flex flex-row justify-center align-center text-center ' + classes
-    "
-    style="z-index: 1006"
+  <div
+    :class="'alert-bar ' + (onClick ? 'clickable' : '')"
     @click="onClick !== undefined ? onClick() : null"
     @click="onClick !== undefined ? onClick() : null"
   >
   >
-    <!-- Forcing z-index to avoid this : https://github.com/vuetifyjs/nuxt-module/issues/205 -->
     <slot>
     <slot>
       <v-icon v-if="icon" small :icon="icon" />
       <v-icon v-if="icon" small :icon="icon" />
       {{ text }}
       {{ text }}
     </slot>
     </slot>
-  </v-system-bar>
+  </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
+import {useDisplay} from 'vuetify';
+
 const props = defineProps({
 const props = defineProps({
   text: {
   text: {
     type: String,
     type: String,
@@ -31,34 +28,26 @@ const props = defineProps({
     required: false,
     required: false,
     default: undefined,
     default: undefined,
   },
   },
-  backgroundColor: {
-    type: String,
-    required: false,
-    default: 'neutral-soft',
-  },
-  textColor: {
-    type: String,
-    required: false,
-    default: 'on-neutral-soft',
-  },
   onClick: {
   onClick: {
     type: Function,
     type: Function,
     required: false,
     required: false,
     default: undefined,
     default: undefined,
   },
   },
 })
 })
-
-// TODO: voir si possible d'utiliser les variables sass à la place?
-const classes = [
-  'bg-' + props.backgroundColor,
-  'text-' + props.textColor,
-  props.onClick !== undefined ? 'clickable' : '',
-].join(' ')
 </script>
 </script>
 
 
+
 <style scoped lang="scss">
 <style scoped lang="scss">
-.v-system-bar {
+.alert-bar {
+  position: relative;
   font-size: 14px;
   font-size: 14px;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+  padding: 12px;
+  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
 }
 }
 
 
 .v-icon {
 .v-icon {

+ 8 - 4
composables/utils/useRedirect.ts

@@ -4,19 +4,23 @@ export const useRedirect = () => {
   const runtimeConfig = useRuntimeConfig()
   const runtimeConfig = useRuntimeConfig()
 
 
   const redirectToLogout = () => {
   const redirectToLogout = () => {
-    if (!runtimeConfig.baseUrlAdminLegacy) {
+    const baseUrl = runtimeConfig.baseUrlAdminLegacy ?? runtimeConfig.public.baseUrlAdminLegacy
+
+    if (!baseUrl) {
       throw new Error('Configuration error : no redirection target')
       throw new Error('Configuration error : no redirection target')
     }
     }
-    navigateTo(UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#/logout'), {
+    navigateTo(UrlUtils.join(baseUrl, '#/logout'), {
       external: true,
       external: true,
     })
     })
   }
   }
 
 
   const redirectToHome = () => {
   const redirectToHome = () => {
-    if (!runtimeConfig.baseUrlAdminLegacy) {
+    const baseUrl = runtimeConfig.baseUrlAdminLegacy ?? runtimeConfig.public.baseUrlAdminLegacy
+
+    if (!baseUrl) {
       throw new Error('Configuration error : no redirection target')
       throw new Error('Configuration error : no redirection target')
     }
     }
-    navigateTo(UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#/dashboard'), {
+    navigateTo(UrlUtils.join(baseUrl, '#/dashboard'), {
       external: true,
       external: true,
     })
     })
   }
   }

+ 46 - 26
i18n/lang/fr.json

@@ -195,37 +195,44 @@
   "OTHER": "Autre",
   "OTHER": "Autre",
   "CONTACT": "Contact",
   "CONTACT": "Contact",
   "cycle": "Cycle",
   "cycle": "Cycle",
-  "timing": "Durée d'un enseignement (en minutes)",
-  "educationTiming": "Durée d'un enseignement (en minutes)",
-  "new_education_timings": "Nouvelle durée d'enseignement",
+  "teaching_cycles": "Cycles d'enseignement",
+  "timing_title": "Durée des cours",
+  "timing": "Durée des cours (en minutes)",
+  "educationTiming": "Durée des cours (en minutes)",
+  "new_education_timings": "Nouvelle durée de cours",
   "superAdmin": "Compte super-admin",
   "superAdmin": "Compte super-admin",
   "username": "Nom d'utilisateur",
   "username": "Nom d'utilisateur",
   "residenceArea": "Zones de résidence",
   "residenceArea": "Zones de résidence",
-  "deactivateOpentalentSiteWeb": "Désactiver le site opentalent",
+  "activateOpentalentSiteWeb": "Activer le site Opentalent",
+  "deactivateOpentalentSiteWeb": "Désactiver le site Opentalent",
   "reactivateOpentalentSiteWeb": "Réactiver le site Opentalent",
   "reactivateOpentalentSiteWeb": "Réactiver le site Opentalent",
   "passwordSMS": "Mot de passe SMS",
   "passwordSMS": "Mot de passe SMS",
   "usernameSMS": "Nom d'utilisateur SMS",
   "usernameSMS": "Nom d'utilisateur SMS",
-  "smsSenderName": "Personnaliser le nom de l'expéditeur SMS",
+  "smsSenderName": "Nom d'expéditeur SMS personnalisé",
   "attendance": "Absences",
   "attendance": "Absences",
   "parameters_attendances_page": "Absences",
   "parameters_attendances_page": "Absences",
-  "attendanceBookingReason": "Motif d'absence / retard",
-  "attendanceBookingReasons": "Motifs d'absence / retard",
-  "new_attendance_booking_reason": "Nouveau motif d'absence / retard",
+  "alert_configuration": "Configuration des alertes",
+  "attendanceBookingReason": "Motif d'absence ou de retard",
+  "attendanceBookingReasons": "Motifs d'absence ou de retard",
+  "new_attendance_booking_reason": "Nouveau motif d'absence ou de retard",
   "reason": "Motif",
   "reason": "Motif",
   "notifyAdministrationAbsence": "Prévenir l'administrateur en cas d'absences consécutives",
   "notifyAdministrationAbsence": "Prévenir l'administrateur en cas d'absences consécutives",
+  "numberConsecutiveAbsences": "Nombre d'absences consécutives",
   "sendAttendanceEmail": "Prévenir automatiquement la famille par mail en cas d'absence non justifiée",
   "sendAttendanceEmail": "Prévenir automatiquement la famille par mail en cas d'absence non justifiée",
   "sendAttendanceSms": "Prévenir automatiquement la famille par sms en cas d'absence non justifiée",
   "sendAttendanceSms": "Prévenir automatiquement la famille par sms en cas d'absence non justifiée",
   "bulletinReceiver": "Adresser le bulletin à",
   "bulletinReceiver": "Adresser le bulletin à",
-  "bulletinEditWithoutEvaluation": "Editer également les bulletins ne contenant aucune évaluation",
-  "bulletinShowAverages": "Afficher les moyennes",
-  "bulletinShowAbsences": "Afficher les absences",
-  "bulletinViewTestResults": "Afficher les résultats des examens",
-  "bulletinShowEducationWithoutEvaluation": "Afficher les enseignements ne contenant aucune évaluation",
-  "bulletinDisplayLevelAcquired": "Affichage niveau acquis",
-  "bulletinSignatureDirector": "Un cadre « Tampon / Signature » pour l'administration",
-  "bulletinPrintAddress": "L'adresse postale de l'élève ou son tuteur",
-  "bulletinWithTeacher": "Le nom du professeur",
+  "bulletinEditWithoutEvaluation": "Éditer également les bulletins ne contenant aucune évaluation",
+  "bulletinShowAverages": "Moyennes",
+  "bulletinShowAbsences": "Absences",
+  "bulletinViewTestResults": "Résultats des examens",
+  "bulletinShowEducationWithoutEvaluation": "Enseignements ne contenant aucune évaluation",
+  "bulletinDisplayLevelAcquired": "Niveau acquis",
+  "bulletinSignatureDirector": "Cadre « Tampon / Signature » pour l'administration",
+  "bulletinPrintAddress": "Adresse postale de l'élève ou son tuteur",
+  "bulletinWithTeacher": "Nom du professeur",
   "bulletinCriteriaSort": "Ordre de tri des critères",
   "bulletinCriteriaSort": "Ordre de tri des critères",
+  "itemsToDisplayOnBulletins": "Eléments à afficher sur les bulletins",
+  "bulletinSettings": "Configuration",
   "superAdminEmail": "Adresse mail associée",
   "superAdminEmail": "Adresse mail associée",
   "bulletin_parameters": "Bulletins",
   "bulletin_parameters": "Bulletins",
   "sms": "Sms",
   "sms": "Sms",
@@ -243,7 +250,7 @@
   "qrCode": "QrCode",
   "qrCode": "QrCode",
   "qrCodeForLicence": "QrCode pour la licence",
   "qrCodeForLicence": "QrCode pour la licence",
   "studentsAreAdherents": "Les élèves sont également adhérents de l'association",
   "studentsAreAdherents": "Les élèves sont également adhérents de l'association",
-  "showAdherentList": "Afficher la liste des adhérents et leurs coordonnées",
+  "showAdherentList": "Autoriser l'affichage de la liste des adhérents de votre structure, avec leurs coordonnées, dans le compte utilisateur de vos membres.",
   "endCourseDate": "Date de fin des cours ",
   "endCourseDate": "Date de fin des cours ",
   "startCourseDate": "Date de début des cours ",
   "startCourseDate": "Date de début des cours ",
   "generalParams": "Paramètres généraux",
   "generalParams": "Paramètres généraux",
@@ -505,6 +512,7 @@
   "advanced_modification": "Administration site internet",
   "advanced_modification": "Administration site internet",
   "simple_modification": "Modifications simplifiées",
   "simple_modification": "Modifications simplifiées",
   "create": "Créer",
   "create": "Créer",
+  "edit": "Modifier",
   "help_access": "Accès aide",
   "help_access": "Accès aide",
   "configuration": "Configuration",
   "configuration": "Configuration",
   "organization_page": "Fiche de la structure",
   "organization_page": "Fiche de la structure",
@@ -514,6 +522,7 @@
   "cmf_licence_details_url": "Consulter les avantages de la licence CMF",
   "cmf_licence_details_url": "Consulter les avantages de la licence CMF",
   "generate": "Générer",
   "generate": "Générer",
   "parameters": "Préférences",
   "parameters": "Préférences",
+  "parameters_page": "Préférences",
   "places": "Lieux",
   "places": "Lieux",
   "education": "Enseignements",
   "education": "Enseignements",
   "tags": "Tags",
   "tags": "Tags",
@@ -632,7 +641,7 @@
   "start_date_of_activity_season": "Début de saison d'activité",
   "start_date_of_activity_season": "Début de saison d'activité",
   "start_date_of_courses": "Date de début des cours",
   "start_date_of_courses": "Date de début des cours",
   "end_date_of_courses": "Date de fin des cours",
   "end_date_of_courses": "Date de fin des cours",
-  "show_adherents_list_and_their_coordinates": "Afficher la liste des adhérents et leurs coordonnées",
+  "show_adherents_list_and_their_coordinates": "Autoriser l'affichage de la liste des adhérents de votre structure, avec leurs coordonnées, dans le compte utilisateur de vos membres.",
   "students_are_also_association_members": "Les élèves sont adhérents également de l'association",
   "students_are_also_association_members": "Les élèves sont adhérents également de l'association",
   "parameters_general_page": "Paramètres généraux",
   "parameters_general_page": "Paramètres généraux",
   "general_parameters_breadcrumbs": "Paramètres généraux",
   "general_parameters_breadcrumbs": "Paramètres généraux",
@@ -654,14 +663,20 @@
   "parameters_residence_areas_page": "Zones de résidence",
   "parameters_residence_areas_page": "Zones de résidence",
   "parameters_sms_page": "Option SMS",
   "parameters_sms_page": "Option SMS",
   "sms_option": "Option SMS",
   "sms_option": "Option SMS",
+  "sms_option_configuration": "Configuration de l'option SMS",
+  "sms_option_configuration_notice": "Pour utiliser l'option SMS, renseignez les informations d'identification Mobyt de votre structure",
+  "sms_option_configuration_tip": "Pour utiliser l'option SMS, renseignez les informations d'identification Mobyt de votre structure",
   "sms_breadcrumbs": "SMS",
   "sms_breadcrumbs": "SMS",
   "super_admin": "Compte super-admin",
   "super_admin": "Compte super-admin",
   "parameters_super_admin_page": "Compte super-admin",
   "parameters_super_admin_page": "Compte super-admin",
   "super_admin_breadcrumbs": "Compte super-admin",
   "super_admin_breadcrumbs": "Compte super-admin",
   "an_error_happened": "Une erreur s'est produite",
   "an_error_happened": "Une erreur s'est produite",
-  "your_opentalent_website_address_is": "L'adresse de votre site Opentalent est",
+  "your_website": "Votre site web",
+  "your_website_address_is": "L'adresse de votre site internet est",
   "record_a_new_subdomain": "Enregistrer un nouveau sous-domaine",
   "record_a_new_subdomain": "Enregistrer un nouveau sous-domaine",
+  "record_a_new_subdomain_short": "Nouveau sous-domaine",
   "your_subdomains": "Vos sous-domaines",
   "your_subdomains": "Vos sous-domaines",
+  "other_website": "Autre site internet",
   "Not Found": "Données non trouvée",
   "Not Found": "Données non trouvée",
   "subdomains_breadcrumbs": "Sous-domaines",
   "subdomains_breadcrumbs": "Sous-domaines",
   "new_breadcrumbs": "Nouveau",
   "new_breadcrumbs": "Nouveau",
@@ -671,10 +686,11 @@
   "This organization can not register new subdomains": "Nombre maximum de sous-domaines enregistrés atteint",
   "This organization can not register new subdomains": "Nombre maximum de sous-domaines enregistrés atteint",
   "This subdomain is not available": "Ce sous-domaine n'est pas disponible",
   "This subdomain is not available": "Ce sous-domaine n'est pas disponible",
   "This subdomain is already registered": "Ce sous-domaine est déjà enregistré",
   "This subdomain is already registered": "Ce sous-domaine est déjà enregistré",
+  "activate_a_subdomain": "Activer un sous-domaine",
   "subdomain_activated_and_available_in_a_few_minutes": "Le sous-domaine a bien été activé, et sera accessible d'ici quelques minutes",
   "subdomain_activated_and_available_in_a_few_minutes": "Le sous-domaine a bien été activé, et sera accessible d'ici quelques minutes",
   "unknown": "Inconnu",
   "unknown": "Inconnu",
   "allow_teachers_to_generate_attendance_reports": "Autoriser les professeurs à générer des fiches de présence",
   "allow_teachers_to_generate_attendance_reports": "Autoriser les professeurs à générer des fiches de présence",
-  "send_teachers_mail_reports_copy_to_administration": "Mettre l'administration en copie du rapport d'envoi des mails envoyés par les professeurs dans le logiciel",
+  "send_teachers_mail_reports_copy_to_administration": "Envoyer à l'administration une copie par email de chaque message envoyé par les professeurs.",
   "allow_members_to_change_their_names_and_firstnames": "Autoriser les membres à modifier leur nom et prénom",
   "allow_members_to_change_their_names_and_firstnames": "Autoriser les membres à modifier leur nom et prénom",
   "allow_teachers_to_consult_colleagues_informations": "Autoriser les professeurs à consulter le listing de leurs collègues (noms, prénoms, et coordonnées)",
   "allow_teachers_to_consult_colleagues_informations": "Autoriser les professeurs à consulter le listing de leurs collègues (noms, prénoms, et coordonnées)",
   "allow_students_to_consult_their_pedagogical_followup": "Autoriser les élèves à consulter leur suivi pédagogique",
   "allow_students_to_consult_their_pedagogical_followup": "Autoriser les élèves à consulter leur suivi pédagogique",
@@ -687,11 +703,11 @@
   "OUT_CYCLE": "Hors cycle",
   "OUT_CYCLE": "Hors cycle",
   "originalLabel": "Libellés d'origine",
   "originalLabel": "Libellés d'origine",
   "effectiveLabel": "Libellés actuellement utilisés",
   "effectiveLabel": "Libellés actuellement utilisés",
-  "allow_to_configure_teachings_with_played_instrument_choice": "Permettre de configurer les enseignements avec le choix sur l'instrument joué",
+  "allow_to_configure_teachings_with_played_instrument_choice": "Permettre de configurer un enseignement comme une pratique collective, avec précision sur l'activité de l'élève",
   "label": "Libellé",
   "label": "Libellé",
   "undefined": "Indéfini",
   "undefined": "Indéfini",
-  "define_validation_periods_for_teachers": "Définir des périodes de saisie pour les professeurs",
-  "mandatory_validation_for_evaluations": "Valider obligatoirement les évaluations",
+  "define_validation_periods_for_teachers": "Définir les périodes de saisie des évaluations pour les professeurs",
+  "mandatory_validation_for_evaluations": "Valider obligatoirement les évaluations pour qu'elles soient visibles par les élèves",
   "evaluation_criterium_edition_is_admin_only": "Autoriser uniquement l'administration à modifier les critères d'évaluation",
   "evaluation_criterium_edition_is_admin_only": "Autoriser uniquement l'administration à modifier les critères d'évaluation",
   "max_note_for_pedagogical_followup": "Note maximale pour les notes du suivi pédagogique (entre 1 et 100) ",
   "max_note_for_pedagogical_followup": "Note maximale pour les notes du suivi pédagogique (entre 1 et 100) ",
   "Bad Request": "Requête invalide",
   "Bad Request": "Requête invalide",
@@ -704,6 +720,7 @@
   "education_timings_breadcrumbs": "Durée des cours",
   "education_timings_breadcrumbs": "Durée des cours",
   "create_a_new_residence_area": "Créer une nouvelle zone de résidence",
   "create_a_new_residence_area": "Créer une nouvelle zone de résidence",
   "residence_areas_breadcrumbs": "Zones de résidence",
   "residence_areas_breadcrumbs": "Zones de résidence",
+  "edit_resident_area": "Éditer la zone de résidence",
   "super_admin_explanation_text": "Le compte super-admin possède tous les droits de gestion sur votre logiciel. On l’utilise entre autre pour la gestion de votre site internet, pour créer les comptes des membres de votre structure à la première connexion au logiciel, ou dans des situations de dépannage.",
   "super_admin_explanation_text": "Le compte super-admin possède tous les droits de gestion sur votre logiciel. On l’utilise entre autre pour la gestion de votre site internet, pour créer les comptes des membres de votre structure à la première connexion au logiciel, ou dans des situations de dépannage.",
   "exit": "Quitter",
   "exit": "Quitter",
   "max_size_4_mb": "Taille maximum: 4 MO",
   "max_size_4_mb": "Taille maximum: 4 MO",
@@ -720,9 +737,12 @@
   "id": "Id",
   "id": "Id",
   "missing_name": "Nom manquant",
   "missing_name": "Nom manquant",
   "warning": "Avertissement",
   "warning": "Avertissement",
+  "show_warnings": "Afficher les avertissements",
   "please_enter_a_value_for_the_sms_sender_name": "Le nom d'expediteur ne doit pas comporter plus de 11 caractères, et être composé uniquement de chiffres et/ou de lettres.",
   "please_enter_a_value_for_the_sms_sender_name": "Le nom d'expediteur ne doit pas comporter plus de 11 caractères, et être composé uniquement de chiffres et/ou de lettres.",
-  "associated_email": "Adresse Email associée",
-  "An error occured": "Une erreur s'est produite.",
+  "associated_email": "Adresse email associée",
+  "homepage": "Accueil",
+  "go_back_home": "Revenir à l'accueil",
+  "passwordSMS: Invalid Mobyt credentials": "Identifiants SMS non reconnus",
   "you_want_to_stop_your_premium_trial_period": "Vous souhaitez arrêter votre période d’essai Opentalent Artist Premium",
   "you_want_to_stop_your_premium_trial_period": "Vous souhaitez arrêter votre période d’essai Opentalent Artist Premium",
   "stop_trial_period_warning_1a": "En choisissant d’arrêter votre période d'essai, votre compte reviendra automatiquement à la version",
   "stop_trial_period_warning_1a": "En choisissant d’arrêter votre période d'essai, votre compte reviendra automatiquement à la version",
   "stop_trial_period_warning_1b": "sans perte de vos données essentielles.",
   "stop_trial_period_warning_1b": "sans perte de vos données essentielles.",

+ 1 - 1
layouts/default.vue

@@ -20,7 +20,7 @@
       <v-main class="main">
       <v-main class="main">
         <LayoutSubheader />
         <LayoutSubheader />
 
 
-        <LayoutAlertBar class="mt-1" />
+        <LayoutAlertBar />
 
 
         <!-- Page will be rendered here-->
         <!-- Page will be rendered here-->
         <slot />
         <slot />

+ 18 - 8
layouts/parameters.vue

@@ -13,12 +13,14 @@
       <v-main class="main">
       <v-main class="main">
         <LayoutSubheader />
         <LayoutSubheader />
 
 
-        <LayoutAlertBar class="mt-1" />
+        <LayoutAlertBar />
 
 
         <!-- Page will be rendered here-->
         <!-- Page will be rendered here-->
-        <v-card class="parameters-page-card">
+        <div class="inner-container">
+          <h3>{{ pageTitle }}</h3>
+
           <slot />
           <slot />
-        </v-card>
+        </div>
       </v-main>
       </v-main>
 
 
       <LazyLayoutAlertContainer />
       <LazyLayoutAlertContainer />
@@ -31,13 +33,21 @@ import { useLayoutStore } from '~/stores/layout'
 
 
 const layoutStore = useLayoutStore()
 const layoutStore = useLayoutStore()
 layoutStore.name = 'parameters'
 layoutStore.name = 'parameters'
+
+const route = useRoute()
+const i18n = useI18n()
+
+const pageTitle = computed(() => i18n.t(route.name || 'parameters_page'))
+
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-.parameters-page-card {
-  background-color: rgb(var(--v-theme-neutral-very-soft));
-  color: rgb(var(--v-theme-on-neutral-very-soft));
-  margin: 3%;
-  padding: 24px;
+.inner-container {
+  max-width: 1200px;
+  margin: 0 auto;
+
+  h3 {
+    margin: 36px 0 18px 2%;
+  }
 }
 }
 </style>
 </style>

+ 7 - 6
nuxt.config.ts

@@ -127,12 +127,13 @@ export default defineNuxtConfig({
     },
     },
   },
   },
 
 
-  css: [
-    '@/assets/css/global.scss',
-    '@/assets/css/theme.scss',
-    '@/assets/css/import.scss',
-    '@vuepic/vue-datepicker/dist/main.css',
-  ],
+ 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: {
   typescript: {
     strict: true,
     strict: true,

+ 1 - 1
package.json

@@ -32,7 +32,7 @@
     "@nuxtjs/i18n": "^9.1.3",
     "@nuxtjs/i18n": "^9.1.3",
     "@pinia-orm/nuxt": "^1.10.1",
     "@pinia-orm/nuxt": "^1.10.1",
     "@pinia/nuxt": "^0.5.1",
     "@pinia/nuxt": "^0.5.1",
-    "@vuepic/vue-datepicker": "^7.4.0",
+    "@vuepic/vue-datepicker": "^11.0",
     "cleave.js": "^1.6.0",
     "cleave.js": "^1.6.0",
     "date-fns": "^4.1.0",
     "date-fns": "^4.1.0",
     "event-source-polyfill": "^1.0.31",
     "event-source-polyfill": "^1.0.31",

+ 1 - 0
pages/parameters.vue

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

+ 8 - 5
pages/parameters/attendance_booking_reasons/[id].vue

@@ -1,7 +1,6 @@
 <template>
 <template>
-  <LayoutContainer>
-    <div>
-      <h2>{{ $t('attendanceBookingReason') }}</h2>
+  <div>
+    <LayoutParametersSection>
       <UiFormEdition
       <UiFormEdition
         :model="AttendanceBookingReason"
         :model="AttendanceBookingReason"
         go-back-route="/parameters/attendances"
         go-back-route="/parameters/attendances"
@@ -14,13 +13,17 @@
           />
           />
         </template>
         </template>
       </UiFormEdition>
       </UiFormEdition>
-    </div>
-  </LayoutContainer>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import AttendanceBookingReason from '~/models/Booking/AttendanceBookingReason'
 import AttendanceBookingReason from '~/models/Booking/AttendanceBookingReason'
 
 
+definePageMeta({
+  name: 'attendanceBookingReason',
+})
+
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const rules = () => [
 const rules = () => [

+ 8 - 5
pages/parameters/attendance_booking_reasons/new.vue

@@ -1,7 +1,6 @@
 <template>
 <template>
-  <LayoutContainer>
-    <div>
-      <h2>{{ $t('new_attendance_booking_reason') }}</h2>
+  <div>
+    <LayoutParametersSection>
       <UiFormCreation
       <UiFormCreation
         :model="AttendanceBookingReason"
         :model="AttendanceBookingReason"
         go-back-route="/parameters/attendances"
         go-back-route="/parameters/attendances"
@@ -23,14 +22,18 @@
           </v-container>
           </v-container>
         </template>
         </template>
       </UiFormCreation>
       </UiFormCreation>
-    </div>
-  </LayoutContainer>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import AttendanceBookingReason from '~/models/Booking/AttendanceBookingReason'
 import AttendanceBookingReason from '~/models/Booking/AttendanceBookingReason'
 
 
+definePageMeta({
+  name: 'new_attendance_booking_reason',
+})
+
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const rules = () => [
 const rules = () => [

+ 35 - 31
pages/parameters/attendances.vue

@@ -1,43 +1,47 @@
 <template>
 <template>
-  <LayoutContainer>
-    <div v-if="organizationProfile.isSchool">
-      <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"
-            />
+  <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-model="parameters.numberConsecutiveAbsences"
-              field="notifyAdministrationAbsence"
-              :rules="rules()"
-            />
-          </v-col>
-        </v-row>
-      </UiForm>
-
-      <v-divider class="my-10" />
-    </div>
+          <UiInputNumber
+            v-if="parameters.notifyAdministrationAbsence"
+            v-model="parameters.numberConsecutiveAbsences"
+            field="numberConsecutiveAbsences"
+            :rules="rules()"
+          />
+        </v-col>
+      </v-row>
+    </UiForm>
+  </LayoutParametersSection>
 
 
+  <LayoutParametersSection>
     <LayoutParametersEntityTable
     <LayoutParametersEntityTable
       :model="AttendanceBookingReason"
       :model="AttendanceBookingReason"
+      :title="$t('attendanceBookingReasons')"
       :columns-definitions="[{ property: 'reason' }]"
       :columns-definitions="[{ property: 'reason' }]"
     />
     />
-  </LayoutContainer>
+  </LayoutParametersSection>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
 import type { AsyncData } from '#app'
 import type { AsyncData } from '#app'

+ 72 - 63
pages/parameters/bulletin.vue

@@ -1,68 +1,77 @@
 <template>
 <template>
   <LayoutContainer>
   <LayoutContainer>
-    <UiLoadingPanel v-if="pending" />
-    <UiForm v-else v-model="parameters">
-      <v-row>
-        <v-col cols="12">
-          <UiInputCheckbox
-            v-model="parameters.bulletinWithTeacher"
-            field="bulletinWithTeacher"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.bulletinSignatureDirector"
-            field="bulletinSignatureDirector"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.bulletinShowEducationWithoutEvaluation"
-            field="bulletinShowEducationWithoutEvaluation"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.bulletinShowAbsences"
-            field="bulletinShowAbsences"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.bulletinEditWithoutEvaluation"
-            field="bulletinEditWithoutEvaluation"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.bulletinPrintAddress"
-            field="bulletinPrintAddress"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.bulletinDisplayLevelAcquired"
-            field="bulletinDisplayLevelAcquired"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.bulletinViewTestResults"
-            field="bulletinViewTestResults"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.bulletinShowAverages"
-            field="bulletinShowAverages"
-          />
-
-          <UiInputAutocompleteWithEnum
-            v-model="parameters.bulletinReceiver"
-            field="bulletinReceiver"
-            enum-name="organization_bulletin_send_to"
-          />
-
-          <UiInputAutocompleteWithEnum
-            v-model="parameters.bulletinCriteriaSort"
-            field="bulletinCriteriaSort"
-            enum-name="organization_bulletin_criteria_sort"
-          />
-        </v-col>
-      </v-row>
-    </UiForm>
+    <LayoutParametersSection>
+      <UiLoadingPanel v-if="pending" />
+      <UiForm
+        v-else
+        v-model="parameters"
+      >
+        <v-row>
+          <v-col cols="12">
+            <h4 class="mb-8">{{ $t('itemsToDisplayOnBulletins') }}</h4>
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinWithTeacher"
+              field="bulletinWithTeacher"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinSignatureDirector"
+              field="bulletinSignatureDirector"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinShowEducationWithoutEvaluation"
+              field="bulletinShowEducationWithoutEvaluation"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinShowAbsences"
+              field="bulletinShowAbsences"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinPrintAddress"
+              field="bulletinPrintAddress"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinDisplayLevelAcquired"
+              field="bulletinDisplayLevelAcquired"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinViewTestResults"
+              field="bulletinViewTestResults"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinShowAverages"
+              field="bulletinShowAverages"
+            />
+
+            <h4 class="my-8">{{ $t('bulletinSettings') }}</h4>
+
+            <UiInputAutocompleteWithEnum
+              v-model="parameters.bulletinCriteriaSort"
+              field="bulletinCriteriaSort"
+              enum-name="organization_bulletin_criteria_sort"
+            />
+
+            <UiInputAutocompleteWithEnum
+              v-model="parameters.bulletinReceiver"
+              field="bulletinReceiver"
+              enum-name="organization_bulletin_send_to"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.bulletinEditWithoutEvaluation"
+              field="bulletinEditWithoutEvaluation"
+            />
+          </v-col>
+        </v-row>
+      </UiForm>
+    </LayoutParametersSection>
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 

+ 8 - 5
pages/parameters/cycles/[id].vue

@@ -1,19 +1,22 @@
 <template>
 <template>
-  <LayoutContainer>
-    <div>
-      <h2>{{ $t('cycle') }}</h2>
+  <div>
+    <LayoutParametersSection>
       <UiFormEdition :model="Cycle" go-back-route="/parameters/teaching">
       <UiFormEdition :model="Cycle" go-back-route="/parameters/teaching">
         <template #default="{ entity }">
         <template #default="{ entity }">
           <UiInputText v-model="entity.label" field="label" :rules="rules()" />
           <UiInputText v-model="entity.label" field="label" :rules="rules()" />
         </template>
         </template>
       </UiFormEdition>
       </UiFormEdition>
-    </div>
-  </LayoutContainer>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import Cycle from '~/models/Education/Cycle'
 import Cycle from '~/models/Education/Cycle'
 
 
+definePageMeta({
+  name: 'cycle',
+})
+
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const rules = () => [
 const rules = () => [

+ 47 - 42
pages/parameters/education_notation.vue

@@ -1,52 +1,57 @@
 <template>
 <template>
   <LayoutContainer>
   <LayoutContainer>
-    <UiLoadingPanel v-if="pending" />
-    <UiForm v-else v-model="parameters">
-      <v-row>
-        <v-col cols="12">
-          <UiInputCheckbox
-            v-model="parameters.periodValidation"
-            field="periodValidation"
-            label="define_validation_periods_for_teachers"
-          />
+    <LayoutParametersSection>
+      <UiLoadingPanel v-if="pending" />
+      <UiForm
+        v-else
+        v-model="parameters"
+      >
+        <v-row>
+          <v-col cols="12">
+            <UiInputCheckbox
+              v-model="parameters.periodValidation"
+              field="periodValidation"
+              label="define_validation_periods_for_teachers"
+            />
 
 
-          <UiInputCheckbox
-            v-model="parameters.editCriteriaNotationByAdminOnly"
-            field="editCriteriaNotationByAdminOnly"
-            label="evaluation_criterium_edition_is_admin_only"
-          />
+            <UiInputCheckbox
+              v-model="parameters.editCriteriaNotationByAdminOnly"
+              field="editCriteriaNotationByAdminOnly"
+              label="evaluation_criterium_edition_is_admin_only"
+            />
 
 
-          <UiInputCheckbox
-            v-model="parameters.requiredValidation"
-            field="requiredValidation"
-            label="mandatory_validation_for_evaluations"
-          />
+            <UiInputCheckbox
+              v-model="parameters.requiredValidation"
+              field="requiredValidation"
+              label="mandatory_validation_for_evaluations"
+            />
 
 
-          <UiInputAutocompleteWithEnum
-            v-if="organizationProfile.hasModule('AdvancedEducationNotation')"
-            v-model="parameters.advancedEducationNotationType"
-            enum-name="advanced_education_notation"
-            field="advancedEducationNotationType"
-          />
+            <UiInputAutocompleteWithEnum
+              v-if="organizationProfile.hasModule('AdvancedEducationNotation')"
+              v-model="parameters.advancedEducationNotationType"
+              enum-name="advanced_education_notation"
+              field="advancedEducationNotationType"
+            />
 
 
-          <UiInputAutocompleteWithEnum
-            v-model="parameters.educationPeriodicity"
-            enum-name="education_periodicity"
-            field="educationPeriodicity"
-          />
+            <UiInputAutocompleteWithEnum
+              v-model="parameters.educationPeriodicity"
+              enum-name="education_periodicity"
+              field="educationPeriodicity"
+            />
 
 
-          <UiInputNumber
-            v-model="parameters.average"
-            field="average"
-            label="max_note_for_pedagogical_followup"
-            :default="20"
-            :min="1"
-            :max="100"
-            class="mt-2"
-          />
-        </v-col>
-      </v-row>
-    </UiForm>
+            <UiInputNumber
+              v-model="parameters.average"
+              field="average"
+              label="max_note_for_pedagogical_followup"
+              :default="20"
+              :min="1"
+              :max="100"
+              class="mt-2"
+            />
+          </v-col>
+        </v-row>
+      </UiForm>
+    </LayoutParametersSection>
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 

+ 8 - 5
pages/parameters/education_timings/[id].vue

@@ -1,7 +1,6 @@
 <template>
 <template>
-  <LayoutContainer>
-    <div>
-      <h2>{{ $t('educationTiming') }}</h2>
+  <div>
+    <LayoutParametersSection>
       <UiFormEdition
       <UiFormEdition
         :model="EducationTiming"
         :model="EducationTiming"
         go-back-route="/parameters/education_timings"
         go-back-route="/parameters/education_timings"
@@ -14,13 +13,17 @@
           />
           />
         </template>
         </template>
       </UiFormEdition>
       </UiFormEdition>
-    </div>
-  </LayoutContainer>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import EducationTiming from '~/models/Education/EducationTiming'
 import EducationTiming from '~/models/Education/EducationTiming'
 
 
+definePageMeta({
+  name: 'educationTiming',
+})
+
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const rules = () => [
 const rules = () => [

+ 6 - 4
pages/parameters/education_timings/index.vue

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

+ 8 - 5
pages/parameters/education_timings/new.vue

@@ -1,7 +1,6 @@
 <template>
 <template>
-  <LayoutContainer>
-    <div>
-      <h2>{{ $t('new_education_timing') }}</h2>
+  <div>
+    <LayoutParametersSection>
       <UiFormCreation
       <UiFormCreation
         :model="EducationTiming"
         :model="EducationTiming"
         go-back-route="/parameters/education_timings"
         go-back-route="/parameters/education_timings"
@@ -23,14 +22,18 @@
           </v-container>
           </v-container>
         </template>
         </template>
       </UiFormCreation>
       </UiFormCreation>
-    </div>
-  </LayoutContainer>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import EducationTiming from '~/models/Education/EducationTiming'
 import EducationTiming from '~/models/Education/EducationTiming'
 
 
+definePageMeta({
+  name: 'new_education_timing',
+})
+
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const rules = () => [
 const rules = () => [

+ 76 - 72
pages/parameters/general_parameters.vue

@@ -1,84 +1,88 @@
 <template>
 <template>
   <LayoutContainer>
   <LayoutContainer>
-    <UiLoadingPanel v-if="pending" />
-    <UiForm v-else-if="parameters !== null" v-model="parameters">
-      <v-row>
-        <v-col cols="12">
-          <UiInputDatePicker
-            v-if="organizationProfile.isSchool"
-            v-model="parameters.financialDate"
-            field="financialDate"
-            label="start_date_of_financial_season"
-            position="left"
-            class="my-2"
-          />
-
-          <UiInputDatePicker
-            v-if="organizationProfile.isSchool"
-            v-model="parameters.startCourseDate"
-            field="startCourseDate"
-            label="start_date_of_courses"
-            position="left"
-            class="my-2"
-          />
+    <LayoutParametersSection>
+      <UiLoadingPanel v-if="pending" />
+      <UiForm
+        v-else-if="parameters !== null"
+        v-model="parameters"
+      >
+        <v-row>
+          <v-col cols="12">
+            <UiInputDatePicker
+              v-if="organizationProfile.isSchool"
+              v-model="parameters.financialDate"
+              field="financialDate"
+              label="start_date_of_financial_season"
+              position="left"
+              class="my-2"
+            />
 
 
-          <UiInputCheckbox
-            v-model="parameters.showAdherentList"
-            field="showAdherentList"
-            label="show_adherents_list_and_their_coordinates"
-          />
+            <UiInputDatePicker
+              v-if="organizationProfile.isSchool"
+              v-model="parameters.musicalDate"
+              field="musicalDate"
+              label="start_date_of_activity_season"
+              position="left"
+              class="my-2"
+            />
 
 
-          <UiInputAutocompleteWithEnum
-            v-model="parameters.timezone"
-            enum-name="timezone"
-            field="timezone"
-            variant="underlined"
-          />
+            <UiInputDatePicker
+              v-if="organizationProfile.isSchool"
+              v-model="parameters.startCourseDate"
+              field="startCourseDate"
+              label="start_date_of_courses"
+              position="left"
+              class="my-2"
+            />
 
 
-          <UiInputDatePicker
-            v-if="organizationProfile.isSchool"
-            v-model="parameters.musicalDate"
-            field="musicalDate"
-            label="start_date_of_activity_season"
-            position="left"
-            class="my-2"
-          />
+            <UiInputDatePicker
+              v-if="organizationProfile.isSchool"
+              v-model="parameters.endCourseDate"
+              field="endCourseDate"
+              label="end_date_of_courses"
+              position="left"
+              class="my-2"
+            />
 
 
-          <UiInputDatePicker
-            v-if="organizationProfile.isSchool"
-            v-model="parameters.endCourseDate"
-            field="endCourseDate"
-            label="end_date_of_courses"
-            position="left"
-            class="my-2"
-          />
+            <UiInputAutocompleteWithEnum
+              v-model="parameters.timezone"
+              enum-name="timezone"
+              field="timezone"
+            />
 
 
-          <UiInputCheckbox
-            v-if="
-              organizationProfile.isSchool && organizationProfile.isAssociation
-            "
-            v-model="parameters.studentsAreAdherents"
-            field="studentsAreAdherents"
-            label="students_are_also_association_members"
-          />
+            <UiInputCheckbox
+              v-model="parameters.showAdherentList"
+              field="showAdherentList"
+              label="show_adherents_list_and_their_coordinates"
+            />
 
 
-          <div
-            v-if="organizationProfile.isCMFCentralService"
-            class="d-flex flex-column"
-          >
-            <span class="mb-1 v-label" style="font-size: 12px"
-              >{{ $t('qrCode') }}
-            </span>
-            <UiInputImage
-              v-model="parameters.qrCode"
-              field="qrCode"
-              label="licenceQrCode"
-              :width="120"
+            <UiInputCheckbox
+              v-if="
+                organizationProfile.isSchool && organizationProfile.isAssociation
+              "
+              v-model="parameters.studentsAreAdherents"
+              field="studentsAreAdherents"
+              label="students_are_also_association_members"
             />
             />
-          </div>
-        </v-col>
-      </v-row>
-    </UiForm>
+
+            <div
+              v-if="organizationProfile.isCMFCentralService"
+              class="d-flex flex-column"
+            >
+              <span class="mb-1 v-label" style="font-size: 12px"
+                >{{ $t('licenceQrCode') }}
+              </span>
+              <UiInputImage
+                v-model="parameters.qrCode"
+                field="qrCode"
+                :width="120"
+                :cropping-enabled="false"
+              />
+            </div>
+          </v-col>
+        </v-row>
+      </UiForm>
+    </LayoutParametersSection>
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 

+ 45 - 39
pages/parameters/intranet.vue

@@ -1,49 +1,55 @@
 <template>
 <template>
   <LayoutContainer>
   <LayoutContainer>
-    <UiLoadingPanel v-if="pending" />
-    <UiForm v-else v-model="parameters">
-      <v-row>
-        <v-col cols="12">
-          <h3>{{ $t('teachers') }}</h3>
-          <UiInputCheckbox
-            v-model="parameters.createCourse"
-            field="createCourse"
-            label="allow_teachers_to_create_courses"
-          />
+    <LayoutParametersSection>
+      <UiLoadingPanel v-if="pending" />
+      <UiForm
+        v-else
+        v-model="parameters"
+      >
+        <v-row>
+          <v-col cols="12">
+            <h4 class="mb-4">{{ $t('teachers')}}</h4>
 
 
-          <UiInputCheckbox
-            v-model="parameters.consultTeacherListing"
-            field="consultTeacherListing"
-            label="allow_teachers_to_consult_colleagues_informations"
-          />
+            <UiInputCheckbox
+              v-model="parameters.createCourse"
+              field="createCourse"
+              label="allow_teachers_to_create_courses"
+            />
 
 
-          <UiInputCheckbox
-            v-model="parameters.consultPedagogicResult"
-            field="showAdherentList"
-            label="allow_students_to_consult_their_pedagogical_followup"
-          />
+            <UiInputCheckbox
+              v-model="parameters.consultTeacherListing"
+              field="consultTeacherListing"
+              label="allow_teachers_to_consult_colleagues_informations"
+            />
 
 
-          <UiInputCheckbox
-            v-model="parameters.generateAttendanceReport"
-            field="generateAttendanceReport"
-            label="allow_teachers_to_generate_attendance_reports"
-          />
+            <UiInputCheckbox
+              v-model="parameters.consultPedagogicResult"
+              field="showAdherentList"
+              label="allow_students_to_consult_their_pedagogical_followup"
+            />
 
 
-          <h3>{{ $t('pupils-members') }}</h3>
-          <UiInputCheckbox
-            v-model="parameters.administrationCc"
-            field="administrationCc"
-            label="send_teachers_mail_reports_copy_to_administration"
-          />
+            <UiInputCheckbox
+              v-model="parameters.generateAttendanceReport"
+              field="generateAttendanceReport"
+              label="allow_teachers_to_generate_attendance_reports"
+            />
 
 
-          <UiInputCheckbox
-            v-model="parameters.allowMembersToChangeGivenNameAndName"
-            field="allowMembersToChangeGivenNameAndName"
-            label="allow_members_to_change_their_names_and_firstnames"
-          />
-        </v-col>
-      </v-row>
-    </UiForm>
+            <h4 class="mt-3 mb-4">{{ $t('pupils-members')}}</h4>
+            <UiInputCheckbox
+              v-model="parameters.administrationCc"
+              field="administrationCc"
+              label="send_teachers_mail_reports_copy_to_administration"
+            />
+
+            <UiInputCheckbox
+              v-model="parameters.allowMembersToChangeGivenNameAndName"
+              field="allowMembersToChangeGivenNameAndName"
+              label="allow_members_to_change_their_names_and_firstnames"
+            />
+          </v-col>
+        </v-row>
+      </UiForm>
+    </LayoutParametersSection>
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 

+ 8 - 5
pages/parameters/residence_areas/[id].vue

@@ -1,7 +1,6 @@
 <template>
 <template>
-  <LayoutContainer>
-    <div>
-      <h2>Éditer la zone de résidence</h2>
+  <div>
+    <LayoutParametersSection>
       <UiFormEdition
       <UiFormEdition
         :model="ResidenceArea"
         :model="ResidenceArea"
         go-back-route="/parameters/residence_areas"
         go-back-route="/parameters/residence_areas"
@@ -15,14 +14,18 @@
           />
           />
         </template>
         </template>
       </UiFormEdition>
       </UiFormEdition>
-    </div>
-  </LayoutContainer>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import ResidenceArea from '~/models/Billing/ResidenceArea'
 import ResidenceArea from '~/models/Billing/ResidenceArea'
 
 
+definePageMeta({
+  name: 'edit_resident_area',
+})
+
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const rules = () => [
 const rules = () => [

+ 8 - 6
pages/parameters/residence_areas/index.vue

@@ -1,10 +1,12 @@
 <template>
 <template>
-  <LayoutContainer>
-    <LayoutParametersEntityTable
-      :model="ResidenceArea"
-      :columns-definitions="[{ property: 'label' }]"
-    />
-  </LayoutContainer>
+  <LayoutParametersSection>
+    <LayoutContainer>
+      <LayoutParametersEntityTable
+        :model="ResidenceArea"
+        :columns-definitions="[{ property: 'label' }]"
+      />
+    </LayoutContainer>
+  </LayoutParametersSection>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">

+ 8 - 5
pages/parameters/residence_areas/new.vue

@@ -1,7 +1,6 @@
 <template>
 <template>
-  <LayoutContainer>
-    <div>
-      <h2>{{ $t('create_a_new_residence_area') }}</h2>
+  <div>
+    <LayoutParametersSection>
       <UiFormCreation
       <UiFormCreation
         :model="ResidenceArea"
         :model="ResidenceArea"
         go-back-route="/parameters/residence_areas"
         go-back-route="/parameters/residence_areas"
@@ -21,14 +20,18 @@
           </v-container>
           </v-container>
         </template>
         </template>
       </UiFormCreation>
       </UiFormCreation>
-    </div>
-  </LayoutContainer>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import ResidenceArea from '~/models/Billing/ResidenceArea'
 import ResidenceArea from '~/models/Billing/ResidenceArea'
 
 
+definePageMeta({
+  name: 'create_a_new_residence_area',
+})
+
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const rules = () => [
 const rules = () => [

+ 26 - 28
pages/parameters/sms.vue

@@ -1,33 +1,29 @@
 <template>
 <template>
   <div>
   <div>
-    <UiForm v-if="parameters" v-model="parameters">
-      <v-row>
-        <v-col cols="12">
-          <UiInputText
-            v-model="parameters.smsSenderName"
-            field="smsSenderName"
-            :rules="rules()"
-            variant="underlined"
-          />
-        </v-col>
-        <v-col cols="12">
-          <UiInputText
-            v-model="parameters.usernameSMS"
-            field="usernameSMS"
-            label="Nom d'utilisateur SMS"
-            variant="underlined"
-          />
-        </v-col>
-        <v-col cols="12">
-          <UiInputText
-            v-model="parameters.passwordSMS"
-            field="passwordSMS"
-            class="password"
-            variant="underlined"
-          />
-        </v-col>
-      </v-row>
-    </UiForm>
+    <LayoutParametersSection>
+      <UiForm v-if="parameters" v-model="parameters">
+        <v-row>
+          <v-col cols="12">
+            <UiInputText
+              v-model="parameters.usernameSMS"
+              field="usernameSMS"
+            />
+          </v-col>
+          <v-col cols="12">
+            <UiInputText
+              v-model="parameters.passwordSMS"
+              field="passwordSMS"
+              class="password"
+            />
+          </v-col>
+          <v-col cols="12">
+            <div class="mb-3">
+              {{ $t('smsSenderName') }} : <b>{{ parameters.smsSenderName }}</b>
+            </div>
+          </v-col>
+        </v-row>
+      </UiForm>
+    </LayoutParametersSection>
   </div>
   </div>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
@@ -66,6 +62,7 @@ const rules = () => [
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
+
 /**
 /**
 Simule une apparence de saisie de type mot de passe
 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
 Sans ça, les navigateurs proposent la saisie semi auto et la mémorisation du mot de passe
@@ -77,6 +74,7 @@ Sans ça, les navigateurs proposent la saisie semi auto et la mémorisation du m
   font-display: block;
   font-display: block;
   src: url(https://jsbin-user-assets.s3.amazonaws.com/rafaelcastrocouto/password.ttf);
   src: url(https://jsbin-user-assets.s3.amazonaws.com/rafaelcastrocouto/password.ttf);
 }
 }
+
 :deep(.password input) {
 :deep(.password input) {
   font-family: 'password';
   font-family: 'password';
 }
 }

+ 8 - 4
pages/parameters/subdomains/[id].vue

@@ -1,7 +1,7 @@
 <!-- Page de détails d'un sous-domaine -->
 <!-- Page de détails d'un sous-domaine -->
 <template>
 <template>
-  <main>
-    <LayoutContainer>
+  <div>
+    <LayoutParametersSection>
       <UiLoadingPanel v-if="pending" />
       <UiLoadingPanel v-if="pending" />
       <div v-else-if="subdomain !== null">
       <div v-else-if="subdomain !== null">
         <div>{{ $t('youRegisteredTheFollowingSubdomain') }} :</div>
         <div>{{ $t('youRegisteredTheFollowingSubdomain') }} :</div>
@@ -32,8 +32,8 @@
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
-    </LayoutContainer>
-  </main>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
@@ -46,6 +46,10 @@ import { TYPE_ALERT } from '~/types/enum/enums'
 import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
 import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
 import { useRouteUtils } from '~/composables/utils/useRouteUtils'
 import { useRouteUtils } from '~/composables/utils/useRouteUtils'
 
 
+definePageMeta({
+  name: 'activate_a_subdomain',
+})
+
 const { em } = useEntityManager()
 const { em } = useEntityManager()
 const { fetch } = useEntityFetch()
 const { fetch } = useEntityFetch()
 
 

+ 8 - 4
pages/parameters/subdomains/new.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
-  <main>
-    <LayoutContainer>
+  <div>
+    <LayoutParametersSection>
       <UiForm
       <UiForm
         ref="form"
         ref="form"
         v-model="subdomain"
         v-model="subdomain"
@@ -45,8 +45,8 @@
           </NuxtLink>
           </NuxtLink>
         </template>
         </template>
       </UiForm>
       </UiForm>
-    </LayoutContainer>
-  </main>
+    </LayoutParametersSection>
+  </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
@@ -60,6 +60,10 @@ import SubdomainValidation from '~/services/validation/subdomainValidation'
 import { useSubdomainValidation } from '~/composables/form/validation/useSubdomainValidation'
 import { useSubdomainValidation } from '~/composables/form/validation/useSubdomainValidation'
 import Form from '~/components/Ui/Form.vue'
 import Form from '~/components/Ui/Form.vue'
 
 
+definePageMeta({
+  name: 'record_a_new_subdomain',
+})
+
 const i18n = useI18n()
 const i18n = useI18n()
 
 
 const { em } = useEntityManager()
 const { em } = useEntityManager()

+ 37 - 42
pages/parameters/super_admin.vue

@@ -1,40 +1,40 @@
 <template>
 <template>
   <div>
   <div>
-    <div class="explanation">
-      <div class="px-4 d-flex flex-row align-center">
-        <v-icon class="theme-info">fa fa-info</v-icon>
+    <LayoutParametersSection>
+      <div class="explanation">
+        <div class="px-4 d-flex flex-row align-center justify-center py-2">
+          <v-icon class="theme-info">fa fa-info</v-icon>
+        </div>
+        <div class="px-2 d-flex flex-row align-center justify-center">
+          {{ $t('super_admin_explanation_text') }}
+        </div>
       </div>
       </div>
-      <div class="px-2">
-        {{ $t('super_admin_explanation_text') }}
-      </div>
-    </div>
 
 
-    <UiLoadingPanel v-if="pending" />
-    <UiForm
-      v-else-if="adminAccess"
-      ref="form"
-      v-model="adminAccess"
-      class="w-100"
-    >
-      <v-table class="mb-4">
-        <tbody>
-          <tr>
-            <td>{{ $t('username') }} :</td>
-            <td>
-              <b>{{ adminAccess.username }}</b>
-            </td>
-          </tr>
-        </tbody>
-      </v-table>
+      <UiLoadingPanel v-if="pending" />
+      <UiForm
+        v-else-if="adminAccess"
+        ref="form"
+        v-model="adminAccess"
+        class="w-100"
+      >
+        <v-table class="mb-4">
+          <tbody>
+            <tr>
+              <td>{{ $t('username') }} :</td>
+              <td><b>{{ adminAccess.username }}</b></td>
+            </tr>
+          </tbody>
+        </v-table>
 
 
-      <UiInputEmail
-        v-model="adminAccess.email"
-        field="email"
-        :label="$t('associated_email')"
-        class="mx-4"
-      />
-    </UiForm>
-    <span v-else>{{ $t('no_admin_access_recorded') }}</span>
+        <UiInputEmail
+          v-model="adminAccess.email"
+          field="email"
+          label="associated_email"
+          class="mx-4"
+        />
+      </UiForm>
+      <span v-else>{{ $t('no_admin_access_recorded') }}</span>
+    </LayoutParametersSection>
   </div>
   </div>
 </template>
 </template>
 
 
@@ -42,7 +42,6 @@
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useAccessProfileStore } from '~/stores/accessProfile'
 import { useAccessProfileStore } from '~/stores/accessProfile'
 import AdminAccess from '~/models/Access/AdminAccess'
 import AdminAccess from '~/models/Access/AdminAccess'
-import { useValidationUtils } from '~/composables/utils/useValidationUtils'
 
 
 definePageMeta({
 definePageMeta({
   name: 'parameters_super_admin_page',
   name: 'parameters_super_admin_page',
@@ -56,15 +55,6 @@ if (accessProfile.id === null) {
 }
 }
 
 
 const { data: adminAccess, pending } = fetch(AdminAccess, accessProfile.id)
 const { data: adminAccess, pending } = fetch(AdminAccess, accessProfile.id)
-
-const i18n = useI18n()
-
-const validationUtils = useValidationUtils()
-
-const rules = () => [
-  (email: string | null) =>
-    (email && validationUtils.validEmail(email)) || i18n.t('email_error'),
-]
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
@@ -87,6 +77,11 @@ const rules = () => [
     height: 28px;
     height: 28px;
     width: 28px;
     width: 28px;
   }
   }
+
+  @media (max-width: 600px) {
+    flex-direction: column;
+    justify-content: center;
+  }
 }
 }
 
 
 .v-table td:first-child {
 .v-table td:first-child {

+ 30 - 24
pages/parameters/teaching.vue

@@ -1,24 +1,31 @@
 <template>
 <template>
   <LayoutContainer>
   <LayoutContainer>
-    <UiLoadingPanel v-if="pending" />
-    <UiForm v-else-if="parameters !== null" v-model="parameters">
+    <LayoutParametersSection>
+      <h4>{{ $t('configuration') }}</h4>
+
+      <UiLoadingPanel v-if="pending" />
+      <UiForm
+        v-else-if="parameters !== null"
+        v-model="parameters"
+      >
+        <UiInputCheckbox
+          v-model="parameters.showEducationIsACollectivePractice"
+          field="showEducationIsACollectivePractice"
+          label="allow_to_configure_teachings_with_played_instrument_choice"
+        />
+      </UiForm>
+    </LayoutParametersSection>
+
+    <LayoutParametersSection>
       <LayoutParametersTable
       <LayoutParametersTable
         :items="tableItems"
         :items="tableItems"
-        :columns-definitions="[
-          { property: 'originalLabel' },
-          { property: 'effectiveLabel' },
-        ]"
+        :title="$t('teaching_cycles')"
+        :columns-definitions="[{ property: 'originalLabel' }, { property: 'effectiveLabel' }]"
         identifier="value"
         identifier="value"
         :actions="[TABLE_ACTION.EDIT]"
         :actions="[TABLE_ACTION.EDIT]"
         @editClicked="goToCycleEditPage"
         @editClicked="goToCycleEditPage"
       />
       />
-
-      <UiInputCheckbox
-        v-model="parameters.showEducationIsACollectivePractice"
-        field="showEducationIsACollectivePractice"
-        label="allow_to_configure_teachings_with_played_instrument_choice"
-      />
-    </UiForm>
+    </LayoutParametersSection>
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 
@@ -32,7 +39,7 @@ import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 import type { AnyJson } from '~/types/data'
 import type { AnyJson } from '~/types/data'
 import { useEnumFetch } from '~/composables/data/useEnumFetch'
 import { useEnumFetch } from '~/composables/data/useEnumFetch'
 import ApiResource from '~/models/ApiResource'
 import ApiResource from '~/models/ApiResource'
-import { TABLE_ACTION } from '~/types/enum/enums'
+import {TABLE_ACTION} from '~/types/enum/enums';
 
 
 definePageMeta({
 definePageMeta({
   name: 'parameters_teaching_page',
   name: 'parameters_teaching_page',
@@ -84,15 +91,13 @@ const orderedCycles: ComputedRef<AnyJson> = computed(() => {
 })
 })
 
 
 const tableItems = computed(() => {
 const tableItems = computed(() => {
-  return (
-    cycleEnum.value?.map((item) => {
-      return {
-        value: item.value,
-        originalLabel: item.label,
-        effectiveLabel: (orderedCycles.value[item.value] ?? item).label,
-      }
-    }) || []
-  )
+  return cycleEnum.value?.map((item) => {
+    return {
+      value: item.value,
+      originalLabel: item.label,
+      effectiveLabel: (orderedCycles.value[item.value] ?? item).label,
+    }
+  }) || []
 })
 })
 
 
 const goToCycleEditPage = (item: object) => {
 const goToCycleEditPage = (item: object) => {
@@ -101,4 +106,5 @@ const goToCycleEditPage = (item: object) => {
 }
 }
 </script>
 </script>
 
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+</style>

+ 105 - 139
pages/parameters/website.vue

@@ -1,136 +1,101 @@
 <template>
 <template>
   <LayoutContainer>
   <LayoutContainer>
-    <UiLoadingPanel v-if="pending" />
-    <UiForm
-      v-else-if="parameters !== null"
-      :model="Parameters"
-      v-model="parameters"
-    >
-      <v-row>
-        <v-col cols="12">
-          <div class="my-5">
-            <span>{{ $t('your_opentalent_website_address_is') }} : </span>
+    <LayoutParametersSection>
+      <h4 class="flex-grow-1 align-self-center">
+        {{ $t('your_website') }}
+      </h4>
+
+      <UiLoadingPanel v-if="pending" />
+      <UiForm
+        v-else-if="parameters !== null"
+        :model="Parameters"
+        v-model="parameters"
+      >
+        <div class="section-header">
+          <div class="flex-grow-1 align-self-center">
+            <span>{{ $t('your_website_address_is') }} : </span>
             <strong class="ml-2">
             <strong class="ml-2">
               <a :href="organizationProfile.website ?? '#'" target="_blank">
               <a :href="organizationProfile.website ?? '#'" target="_blank">
                 {{ organizationProfile.website }}
                 {{ organizationProfile.website }}
               </a>
               </a>
             </strong>
             </strong>
           </div>
           </div>
-
-          <!-- les publicationDirectors sont des entités Access -->
-          <UiInputAutocompleteAccesses
-            v-model="parameters.publicationDirectors"
-            field="publicationDirectors"
-            multiple
-            chips
-            variant="outlined"
-            class="my-4"
+        </div>
+
+        <div
+          v-if="!organizationProfile.isCmf"
+          class="d-flex justify-left mt-6"
+        >
+          <LayoutParametersWebsiteActivationSwitch
+            :model-value="!parameters.desactivateOpentalentSiteWeb"
+            @update:modelValue="parameters.desactivateOpentalentSiteWeb = !parameters.desactivateOpentalentSiteWeb"
           />
           />
-
-          <div class="mb-6">
-            <div>
-              <h4>{{ $t('your_subdomains') }} :</h4>
-            </div>
-            <UiLoadingPanel v-if="subdomainsPending" />
-            <div v-else>
-              <v-table v-if="subdomains!.items" class="subdomains-table my-2">
-                <tbody>
-                  <tr
-                    v-for="subdomain in subdomains!.items"
-                    :key="subdomain.id"
-                    :title="subdomain.subdomain"
-                    :class="
-                      'subdomainItem' + (subdomain.active ? ' active' : '')
-                    "
-                    @click="goToEditPage(subdomain.id)"
-                  >
-                    <td>{{ subdomain.subdomain }}</td>
-                    <td>
-                      <span v-if="subdomain.active">
-                        <v-icon class="text-success icon">
-                          fa-solid fa-check
-                        </v-icon>
-                        {{ $t('active') }}
-                      </span>
-                    </td>
-                  </tr>
-                </tbody>
-              </v-table>
-              <span v-else>{{ $t('no_recorded_subdomain') }}</span>
-
-              <div class="d-flex flex-row justify-center w-100">
-                <v-btn
-                  :disabled="!canAddNewSubdomain"
-                  class="my-5"
-                  @click="onAddSubdomainClick"
-                >
-                  {{ $t('record_a_new_subdomain') }}
-                </v-btn>
-              </div>
-            </div>
-          </div>
-
-          <div v-if="!organizationProfile.isCmf">
-            <v-divider class="my-10" />
-
-            <div
-              v-if="!organizationProfile.isCmf"
-              class="my-8 d-flex flex-row justify-center w-100"
+        </div>
+
+        <!-- les publicationDirectors sont des entités Access -->
+        <UiInputAutocompleteAccesses
+          v-if="!parameters.desactivateOpentalentSiteWeb"
+          v-model="parameters.publicationDirectors"
+          field="publicationDirectors"
+          multiple
+          chips
+          class="my-4"
+        />
+
+        <UiInputText
+          v-model="parameters.otherWebsite"
+          field="otherWebsite"
+          class="my-4"
+        />
+      </UiForm>
+    </LayoutParametersSection>
+
+    <LayoutParametersSection v-if="!parameters.desactivateOpentalentSiteWeb">
+      <div class="section-header">
+        <h4 class="flex-grow-1 align-self-center">
+          {{ $t('your_subdomains') }}
+        </h4>
+      </div>
+
+      <UiLoadingPanel v-if="subdomainsPending" />
+      <div v-else>
+        <v-table v-if="subdomains!.items" class="subdomains-table my-2">
+          <tbody>
+            <tr
+              v-for="subdomain in subdomains!.items"
+              :key="subdomain.id"
+              :title="subdomain.subdomain"
+              :class="
+                'subdomainItem' + (subdomain.active ? ' active' : '')
+              "
+              @click="goToEditPage(subdomain.id)"
             >
             >
-              <v-btn
-                v-if="!parameters.desactivateOpentalentSiteWeb"
-                color="error"
-                @click="showWebsiteDeactivationDialog = true"
-              >
-                {{ $t('deactivateOpentalentSiteWeb') }}
-              </v-btn>
-              <v-btn v-else color="primary" @click="reactivateWebsite">
-                {{ $t('reactivateOpentalentSiteWeb') }}
-              </v-btn>
-
-              <LazyLayoutDialog :show="showWebsiteDeactivationDialog">
-                <template #dialogTitle>
-                  {{ $t('please_confirm') }}
-                </template>
-                <template #dialogText>
-                  <v-col>
-                    <div>
-                      {{
-                        $t(
-                          'yourOpentalentWebsiteWillBeDeactivatedOnceYouLlHaveSaved',
-                        )
-                      }}.
-                    </div>
-                    <span>{{ $t('doYouWantToContinue') }} ?</span>
-                  </v-col>
-                </template>
-                <template #dialogBtn>
-                  <v-btn
-                    class="theme-neutral-soft mr-4"
-                    @click="showWebsiteDeactivationDialog = false"
-                  >
-                    {{ $t('cancel') }}
-                  </v-btn>
-
-                  <v-btn class="theme-primary" @click="onDialogYesBtnClick">
-                    {{ $t('yes') }}
-                  </v-btn>
-                </template>
-              </LazyLayoutDialog>
-            </div>
-          </div>
-
-          <div>
-            <UiInputText
-              v-model="parameters.otherWebsite"
-              field="otherWebsite"
-              variant="underlined"
-              class="my-4"
-            />
-          </div>
-        </v-col>
-      </v-row>
-    </UiForm>
+              <td>{{ subdomain.subdomain }}</td>
+              <td>
+                <span v-if="subdomain.active">
+                  <v-icon class="text-success icon">
+                    fa-solid fa-check
+                  </v-icon>
+                  {{ $t('active') }}
+                </span>
+              </td>
+            </tr>
+          </tbody>
+        </v-table>
+        <span v-else>{{ $t('no_recorded_subdomain') }}</span>
+
+        <div class="d-flex justify-center">
+          <v-btn
+            :disabled="!canAddNewSubdomain"
+            prepend-icon="fa-solid fa-plus"
+            class="my-5"
+            @click="onAddSubdomainClick"
+          >
+            {{ smAndUp ? $t('record_a_new_subdomain') : $t('record_a_new_subdomain_short') }}
+          </v-btn>
+        </div>
+      </div>
+    </LayoutParametersSection>
   </LayoutContainer>
   </LayoutContainer>
 </template>
 </template>
 
 
@@ -144,6 +109,7 @@ import Subdomain from '~/models/Organization/Subdomain'
 import ApiResource from '~/models/ApiResource'
 import ApiResource from '~/models/ApiResource'
 import EqualFilter from '~/services/data/Filters/EqualFilter'
 import EqualFilter from '~/services/data/Filters/EqualFilter'
 import Query from '~/services/data/Query'
 import Query from '~/services/data/Query'
+import {useDisplay} from 'vuetify';
 
 
 definePageMeta({
 definePageMeta({
   name: 'parameters_website_page',
   name: 'parameters_website_page',
@@ -153,6 +119,8 @@ const { fetch, fetchCollection } = useEntityFetch()
 
 
 const organizationProfile = useOrganizationProfileStore()
 const organizationProfile = useOrganizationProfileStore()
 
 
+const { smAndUp } = useDisplay()
+
 if (organizationProfile.parametersId === null) {
 if (organizationProfile.parametersId === null) {
   throw new Error('Missing organization parameters id')
   throw new Error('Missing organization parameters id')
 }
 }
@@ -184,26 +152,24 @@ const onAddSubdomainClick = () => {
   }
   }
   navigateTo('/parameters/subdomains/new')
   navigateTo('/parameters/subdomains/new')
 }
 }
+</script>
 
 
-const showWebsiteDeactivationDialog: Ref<boolean> = ref(false)
+<style scoped lang="scss">
 
 
-const deactivateWebsite = () => {
-  parameters.value!.desactivateOpentalentSiteWeb = true
-}
+.section-header {
+  margin: 10px 0;
+  display: flex;
+  flex-direction: row;
 
 
-const reactivateWebsite = () => {
-  parameters.value!.desactivateOpentalentSiteWeb = false
-}
+  @media (max-width: 600px) {
+    flex-direction: column;
 
 
-const onDialogYesBtnClick = () => {
-  showWebsiteDeactivationDialog.value = false
-  deactivateWebsite()
-}
-</script>
-
-<style scoped lang="scss">
-.subdomains-table {
-  max-width: 450px;
+    :deep(.v-btn) {
+      height: auto;
+      margin: 15px 0;
+      white-space: normal;
+    }
+  }
 }
 }
 
 
 .subdomainItem {
 .subdomainItem {

+ 1 - 1
services/data/enumManager.ts

@@ -23,7 +23,7 @@ class EnumManager {
     const enum_: Enum = []
     const enum_: Enum = []
     for (const key in data.items) {
     for (const key in data.items) {
       if (Object.prototype.hasOwnProperty.call(data.items, key)) {
       if (Object.prototype.hasOwnProperty.call(data.items, key)) {
-        enum_.push({ value: key, label: this.i18n.t(data.items[key]) })
+        enum_.push({ value: data.items[key], label: this.i18n.t(data.items[key]) })
       }
       }
     }
     }
 
 

+ 8 - 9
services/data/imageManager.ts

@@ -37,7 +37,7 @@ class ImageManager {
       return defaultUrl
       return defaultUrl
     }
     }
 
 
-    const imageUrl = `api/download/${id}`
+    const imageUrl = `api/file/download/${id}`
 
 
     // Set requested size if needed
     // Set requested size if needed
     if (height > 0 || width > 0) {
     if (height > 0 || width > 0) {
@@ -49,16 +49,15 @@ class ImageManager {
     // Une image doit toujours avoir le time en options pour éviter les problèmes de cache
     // Une image doit toujours avoir le time en options pour éviter les problèmes de cache
     const query = [this.getCacheKey()]
     const query = [this.getCacheKey()]
 
 
-    const response: Response = await this.apiRequestService.get(imageUrl, query)
-    if (!response) {
-      console.error('Error: image ' + id + ' not found')
-      return defaultUrl
+    const blobPart = await this.apiRequestService.get(imageUrl, query);
+    if (!blobPart) {
+      console.error('Error: image ' + id + ' not found');
+      return defaultUrl;
     }
     }
 
 
-    const blobPart = await response.blob()
-    if (blobPart.size === 0) {
-      console.error('Error: image ' + id + ' is invalid')
-      return defaultUrl
+    if (!(blobPart instanceof Blob) || blobPart.size === 0) {
+      console.error('Error: image ' + id + ' is invalid');
+      return defaultUrl;
     }
     }
 
 
     return await this.toBase64(blobPart)
     return await this.toBase64(blobPart)

+ 2 - 2
services/layout/menuBuilder/abstractMenuBuilder.ts

@@ -85,7 +85,7 @@ abstract class AbstractMenuBuilder implements MenuBuilder {
    * @param {string} label Titre qui sera traduit
    * @param {string} label Titre qui sera traduit
    * @param to
    * @param to
    * @param type
    * @param type
-   * @param noWarning
+   * @param newTab
    * @return {MenuItem}
    * @return {MenuItem}
    */
    */
   protected createItem(
   protected createItem(
@@ -98,7 +98,7 @@ abstract class AbstractMenuBuilder implements MenuBuilder {
     let url: string
     let url: string
     if (type === MENU_LINK_TYPE.INTERNAL) {
     if (type === MENU_LINK_TYPE.INTERNAL) {
       console.warn(
       console.warn(
-        "'createItem()' should not be used for internal links, use 'addChildItemIfAllowed()'",
+        "'createItem()' should not be used for internal links, use 'makeChildren()'",
       )
       )
     }
     }
     switch (type) {
     switch (type) {

+ 1 - 1
services/layout/menuBuilder/accessMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Répertoire
  * Menu Répertoire
  */
  */
 export default class AccessMenuBuilder extends AbstractMenuBuilder {
 export default class AccessMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Access'
+  static override readonly menuName = 'Access'
 
 
   /**
   /**
    * Construit le menu Répertoire, ou null si aucune page accessible
    * Construit le menu Répertoire, ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/accountMenuBuilder.ts

@@ -6,7 +6,7 @@ import AbstractMenuBuilder from '~/services/layout/menuBuilder/abstractMenuBuild
  * Menu Mon compte
  * Menu Mon compte
  */
  */
 export default class AccountMenuBuilder extends AbstractMenuBuilder {
 export default class AccountMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Account'
+  static override readonly menuName = 'Account'
 
 
   /**
   /**
    * Construit le menu Header Configuration ou null si aucune page accessible
    * Construit le menu Header Configuration ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/admin2iosMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Admin 2IOS
  * Menu Admin 2IOS
  */
  */
 export default class Admin2iosMenuBuilder extends AbstractMenuBuilder {
 export default class Admin2iosMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Admin2ios'
+  static override readonly menuName = 'Admin2ios'
 
 
   /**
   /**
    * Construit le menu Administration 2ios ou null si aucune page accessible
    * Construit le menu Administration 2ios ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/agendaMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu agenda
  * Menu agenda
  */
  */
 export default class AgendaMenuBuilder extends AbstractMenuBuilder {
 export default class AgendaMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Agenda'
+  static override readonly menuName = 'Agenda'
 
 
   /**
   /**
    * Construit le menu Agenda ou null si aucune page accessible
    * Construit le menu Agenda ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/basicomptaMenuBuilder.ts

@@ -6,7 +6,7 @@ import AbstractMenuBuilder from '~/services/layout/menuBuilder/abstractMenuBuild
  * Menu Basicompta
  * Menu Basicompta
  */
  */
 export default class BasicomptaMenuBuilder extends AbstractMenuBuilder {
 export default class BasicomptaMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Basicompta'
+  static override readonly menuName = 'Basicompta'
 
 
   build(): MenuItem | null {
   build(): MenuItem | null {
     // cf droit : https://ressources-opentalent.atlassian.net/wiki/spaces/SPEC/pages/32637034/Acc+s+basi+compta+pour+les+structures+de+la+CMF#Acces-a-Basicompta-pour-les-administrateurs
     // cf droit : https://ressources-opentalent.atlassian.net/wiki/spaces/SPEC/pages/32637034/Acc+s+basi+compta+pour+les+structures+de+la+CMF#Acces-a-Basicompta-pour-les-administrateurs

+ 1 - 1
services/layout/menuBuilder/billingMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Facturation
  * Menu Facturation
  */
  */
 export default class BillingMenuBuilder extends AbstractMenuBuilder {
 export default class BillingMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Billing'
+  static override readonly menuName = 'Billing'
 
 
   /**
   /**
    * Construit le menu Facturation ou null si aucune page accessible
    * Construit le menu Facturation ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/communicationMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Communication
  * Menu Communication
  */
  */
 export default class CommunicationMenuBuilder extends AbstractMenuBuilder {
 export default class CommunicationMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Communication'
+  static override readonly menuName = 'Communication'
 
 
   /**
   /**
    * Construit le menu Communication ou null si aucune page accessible
    * Construit le menu Communication ou null si aucune page accessible

+ 2 - 11
services/layout/menuBuilder/configurationMenuBuilder.ts

@@ -7,7 +7,7 @@ import UrlUtils from '~/services/utils/urlUtils'
  * Classe pour la construction du Menu Paramètres
  * Classe pour la construction du Menu Paramètres
  */
  */
 export default class ConfigurationMenuBuilder extends AbstractMenuBuilder {
 export default class ConfigurationMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Configuration'
+  static override readonly menuName = 'Configuration'
 
 
   /**
   /**
    * Construit le menu Header Configuration ou null si aucune page accessible
    * Construit le menu Header Configuration ou null si aucune page accessible
@@ -63,16 +63,7 @@ export default class ConfigurationMenuBuilder extends AbstractMenuBuilder {
     //   )
     //   )
     // }
     // }
 
 
-    if (this.ability.can('display', 'parameters_page')) {
-      children.push(
-        this.createItem(
-          'parameters',
-          undefined,
-          `/main/edit/parameters/${this.organizationProfile.id}`,
-          MENU_LINK_TYPE.V1,
-        ),
-      )
-    }
+    children.push(...this.makeChildren([{ pageName: 'parameters_page' }]))
 
 
     if (this.ability.can('display', 'place_page')) {
     if (this.ability.can('display', 'place_page')) {
       children.push(
       children.push(

+ 1 - 1
services/layout/menuBuilder/cotisationsMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Cotisation (CMF)
  * Menu Cotisation (CMF)
  */
  */
 export default class CotisationsMenuBuilder extends AbstractMenuBuilder {
 export default class CotisationsMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Cotisation'
+  static override readonly menuName = 'Cotisation'
 
 
   /**
   /**
    * Construit le menu Cotisations ou null si aucune page accessible
    * Construit le menu Cotisations ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/donorsMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Donneurs
  * Menu Donneurs
  */
  */
 export default class DonorsMenuBuilder extends AbstractMenuBuilder {
 export default class DonorsMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Donors'
+  static override readonly menuName = 'Donors'
 
 
   /**
   /**
    * Construit le menu Partenariat et Dons, ou null si aucune page accessible
    * Construit le menu Partenariat et Dons, ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/educationalMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Suivi pédagogique
  * Menu Suivi pédagogique
  */
  */
 export default class EducationalMenuBuilder extends AbstractMenuBuilder {
 export default class EducationalMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Educational'
+  static override readonly menuName = 'Educational'
 
 
   /**
   /**
    * Construit le menu Suivi pédagogique ou null si aucune page accessible
    * Construit le menu Suivi pédagogique ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/equipmentMenuBuilder.ts

@@ -6,7 +6,7 @@ import AbstractMenuBuilder from '~/services/layout/menuBuilder/abstractMenuBuild
  * Menu Matériel
  * Menu Matériel
  */
  */
 export default class EquipmentMenuBuilder extends AbstractMenuBuilder {
 export default class EquipmentMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Equipment'
+  static override readonly menuName = 'Equipment'
 
 
   /**
   /**
    * Construit le menu Equipement ou null si aucune page accessible
    * Construit le menu Equipement ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/mainMenuBuilder.ts

@@ -18,7 +18,7 @@ import BasicomptaMenuBuilder from '~/services/layout/menuBuilder/basicomptaMenuB
  * Menu principal (ou menu lateral)
  * Menu principal (ou menu lateral)
  */
  */
 export default class MainMenuBuilder extends AbstractMenuBuilder {
 export default class MainMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Main'
+  static override readonly menuName = 'Main'
 
 
   /**
   /**
    * Construit le menu principal, ou null si aucune page accessible
    * Construit le menu principal, ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/myAccessesMenuBuilder.ts

@@ -7,7 +7,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Mon Profil
  * Menu Mon Profil
  */
  */
 export default class MyAccessesMenuBuilder extends AbstractMenuBuilder {
 export default class MyAccessesMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'MyAccesses'
+  static override readonly menuName = 'MyAccesses'
 
 
   /**
   /**
    * Construit le menu Header Multi compte, ou null si aucune page accessible
    * Construit le menu Header Multi compte, ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/myFamilyMenuBuilder.ts

@@ -7,7 +7,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Famille
  * Menu Famille
  */
  */
 export default class MyFamilyMenuBuilder extends AbstractMenuBuilder {
 export default class MyFamilyMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'MyFamily'
+  static override readonly menuName = 'MyFamily'
 
 
   /**
   /**
    * Construit le menu Header Changement d'utilisateur ou null si aucune page accessible
    * Construit le menu Header Changement d'utilisateur ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/parametersMenuBuilder.ts

@@ -5,7 +5,7 @@ import type { MenuGroup, MenuItems } from '~/types/layout'
  * Menu Paramètres
  * Menu Paramètres
  */
  */
 export default class ParametersMenuBuilder extends AbstractMenuBuilder {
 export default class ParametersMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Parameters'
+  static override readonly menuName = 'Parameters'
 
 
   /**
   /**
    * Construit le menu Header Configuration, ou null si aucune page accessible
    * Construit le menu Header Configuration, ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/rewardsMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Distinctions
  * Menu Distinctions
  */
  */
 export default class RewardsMenuBuilder extends AbstractMenuBuilder {
 export default class RewardsMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'AccessRewards'
+  static override readonly menuName = 'AccessRewards'
 
 
   /**
   /**
    * Construit le menu distinctions, ou null si aucune page accessible
    * Construit le menu distinctions, ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/statsMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Statistiques
  * Menu Statistiques
  */
  */
 export default class StatsMenuBuilder extends AbstractMenuBuilder {
 export default class StatsMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Stats'
+  static override readonly menuName = 'Stats'
 
 
   /**
   /**
    * Construit le menu Statistique et Dons ou null si aucune page accessible
    * Construit le menu Statistique et Dons ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/websiteAdminMenuBuilder.ts

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Site internet
  * Menu Site internet
  */
  */
 export default class WebsiteAdminMenuBuilder extends AbstractMenuBuilder {
 export default class WebsiteAdminMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'WebsiteAdmin'
+  static override readonly menuName = 'WebsiteAdmin'
 
 
   /**
   /**
    * Construit le menu Site internet, ou null si aucune page accessible
    * Construit le menu Site internet, ou null si aucune page accessible

+ 1 - 1
services/layout/menuBuilder/websiteListMenuBuilder.ts

@@ -8,7 +8,7 @@ import type { BaseOrganizationProfile } from '~/types/interfaces'
  * Menu : Liste des sites internet de la structure et de ses structures parentes
  * Menu : Liste des sites internet de la structure et de ses structures parentes
  */
  */
 export default class WebsiteListMenuBuilder extends AbstractMenuBuilder {
 export default class WebsiteListMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'WebsiteList'
+  static override readonly menuName = 'WebsiteList'
 
 
   /**
   /**
    * Construit le menu Site internet, ou null si aucune page accessible
    * Construit le menu Site internet, ou null si aucune page accessible

File diff suppressed because it is too large
+ 301 - 258
yarn.lock


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