Procházet zdrojové kódy

fix merge conflicts

Olivier Massot před 7 měsíci
rodič
revize
e35a052b1c
85 změnil soubory, kde provedl 1749 přidání a 1325 odebrání
  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 {
   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>
   <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>
 </template>
 
 <script setup lang="ts">
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
 import { useAbility } from '@casl/vue'
+import {useDisplay} from 'vuetify';
+import {StencilPreview} from 'vue-advanced-cropper';
 
 const organizationProfile = useOrganizationProfileStore()
 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>
+
+<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 })"
     icon="fas fa-exclamation-triangle"
     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

+ 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">
     <template #prepend>
       <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>
 
-    <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" />
     <!-- En attente validation PO -->
 
     <LayoutHeaderUniversalCreationCreateButton
       v-if="showUniversalButton"
-      class="mr-3"
+      :class="smAndUp ? 'mr-3' : ''"
     />
 
     <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
       :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"
     >
       <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 { 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 showUniversalButton =
@@ -106,9 +116,21 @@ const layoutStore = useLayoutStore()
 </script>
 
 <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 {
-  padding: 14px 14px 13px;
+  padding: 12px 16px;
+  margin-left: 4px;
   font-size: 14px;
   text-decoration: none;
+  height: 100%;
 }
 </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"
     @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-icon
           :icon="icon"
@@ -24,12 +24,16 @@
           class="ma-2 pa-2 align-self-center text-neutral-strong"
         />
       </v-col>
+
       <v-col
         cols="9"
         align-self="center"
         class="pl-2 infos-container flex-grow-1 flex-shrink-1"
       >
-        <h4 class="text-primary">{{ $t(title) }}</h4>
+        <h4 class="text-primary">
+          {{ $t(title) }}
+        </h4>
+
         <p class="text-neutral-strong">
           {{ $t(textContent) }}
         </p>
@@ -43,6 +47,7 @@ import type { PropType } from '@vue/runtime-core'
 import { MENU_LINK_TYPE } from '~/types/enum/layout'
 import { useAdminUrl } from '~/composables/utils/useAdminUrl'
 import UrlUtils from '~/services/utils/urlUtils'
+import {useDisplay} from 'vuetify';
 
 const props = defineProps({
   /**
@@ -96,6 +101,8 @@ const emit = defineEmits(['click'])
 
 const { makeAdminUrl } = useAdminUrl()
 
+const { mdAndUp } = useDisplay()
+
 let url: string | null = null
 
 if (props.href !== null) {

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

@@ -11,7 +11,7 @@
         <v-container v-if="location === 'home'">
           <v-row>
             <!-- 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
                 @click="onCardClick('access')"
                 title="a_person"
@@ -20,7 +20,8 @@
               />
             </v-col>
             <v-col
-              cols="6"
+              cols="12"
+              md="6"
               v-if="
                 ability.can('display', 'agenda_page') &&
                 (ability.can('display', 'course_page') ||
@@ -39,7 +40,8 @@
 
             <!-- Autre évènement -->
             <v-col
-              cols="6"
+              cols="12"
+              md="6"
               v-else-if="
                 ability.can('display', 'agenda_page') &&
                 ability.can('manage', 'events')
@@ -57,7 +59,8 @@
 
             <!-- Une correspondance -->
             <v-col
-              cols="6"
+              cols="12"
+              md="6"
               v-if="
                 ability.can('display', 'message_send_page') &&
                 (ability.can('manage', 'emails') ||
@@ -74,7 +77,11 @@
             </v-col>
 
             <!-- 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
                 title="a_materiel"
                 text-content="add_any_type_material"
@@ -92,7 +99,11 @@
         <v-container v-if="location === 'access'">
           <v-row>
             <!-- Un adhérent -->
-            <v-col cols="6" v-if="isLaw1901">
+            <v-col
+              cols="12"
+              md="6"
+              v-if="isLaw1901"
+            >
               <LayoutHeaderUniversalCreationCard
                 title="an_adherent"
                 text-content="adherent_text_creation_card"
@@ -103,7 +114,11 @@
             </v-col>
 
             <!-- Un membre du CA -->
-            <v-col cols="6" v-if="isLaw1901">
+            <v-col
+              cols="12"
+              md="6"
+              v-if="isLaw1901"
+            >
               <LayoutHeaderUniversalCreationCard
                 title="a_ca_member"
                 text-content="ca_member_text_creation_card"
@@ -114,7 +129,7 @@
             </v-col>
 
             <!-- Un élève -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_student"
                 text-content="student_text_creation_card"
@@ -125,7 +140,7 @@
             </v-col>
 
             <!-- Un tuteur -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_guardian"
                 text-content="guardian_text_creation_card"
@@ -136,7 +151,7 @@
             </v-col>
 
             <!-- Un professeur -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_teacher"
                 text-content="teacher_text_creation_card"
@@ -147,7 +162,7 @@
             </v-col>
 
             <!-- Un membre du personnel -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_member_of_staff"
                 text-content="personnel_text_creation_card"
@@ -158,7 +173,7 @@
             </v-col>
 
             <!-- Une entité légale -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="a_legal_entity"
                 text-content="moral_text_creation_card"
@@ -169,7 +184,7 @@
             </v-col>
 
             <!-- Une inscription en ligne -->
-            <v-col cols="6" v-if="hasOnlineRegistrationModule">
+            <v-col cols="12" md="6" v-if="hasOnlineRegistrationModule">
               <LayoutHeaderUniversalCreationCard
                 title="online_registration"
                 text-content="online_registration_text_creation_card"
@@ -180,7 +195,7 @@
             </v-col>
 
             <!-- Un autre type de contact -->
-            <v-col cols="6">
+            <v-col cols="12" md="6">
               <LayoutHeaderUniversalCreationCard
                 title="another_type_of_contact"
                 text-content="other_contact_text_creation_card"
@@ -196,7 +211,7 @@
         <v-container v-if="location === 'event'">
           <v-row>
             <!-- 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
                 title="course"
                 text-content="course_text_creation_card"
@@ -207,7 +222,7 @@
             </v-col>
 
             <!-- 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
                 title="exam"
                 text-content="exam_text_creation_card"
@@ -219,7 +234,7 @@
 
             <!-- Un projet pédagogique -->
             <v-col
-              cols="6"
+              cols="12" md="6"
               v-if="ability.can('display', 'pedagogics_project_page')"
             >
               <LayoutHeaderUniversalCreationCard
@@ -232,7 +247,7 @@
             </v-col>
 
             <!-- 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
                 to="event-params"
                 href="/calendar/create/events"
@@ -249,7 +264,7 @@
         <v-container v-if="location === 'message'">
           <v-row>
             <!-- 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
                 title="an_email"
                 text-content="email_text_creation_card"
@@ -260,7 +275,7 @@
             </v-col>
 
             <!-- 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
                 title="a_letter"
                 text-content="letter_text_creation_card"
@@ -271,7 +286,7 @@
             </v-col>
 
             <!-- 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
                 title="a_sms"
                 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>
 
     <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? -->
       <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) -->
@@ -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 -->
         <v-list-group
           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 }">
             <v-list-item
@@ -85,9 +93,9 @@ const organizationProfile = useOrganizationProfileStore()
 const { getMenu, hasMenu, isInternalLink, setMenuState, isMenuOpened } =
   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
 const displayMenu = computed(() => {
@@ -152,9 +160,8 @@ function getItems(
   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 {

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

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

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

@@ -1,16 +1,24 @@
 <template>
   <v-navigation-drawer
-    v-if="displayMenu"
-    v-model="isOpened"
+    v-model="displayMenu"
+    :rail="isRail"
+    :disable-resize-watcher="true"
     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
     Il pourra être retiré dès que le bug aura été corrigé
     -->
     <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>
       </div>
     </template>
@@ -22,21 +30,10 @@
         :title="$t(item.label)"
         :prepend-icon="item.icon ? item.icon.name : ''"
         :to="(item as MenuItem).to"
+        @click="onItemClicked"
       >
       </v-list-item>
     </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>
 </template>
 
@@ -47,17 +44,34 @@ import { useMenu } from '~/composables/layout/useMenu'
 import { useHomeUrl } from '~/composables/utils/useHomeUrl'
 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 isOpened = computed(() => isMenuOpened('Parameters'))
+
+// En vue lg+, on affiche toujours le menu
 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, () => {
   // 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(() => {
   unwatch()
 })
+
+// TODO voir à factoriser avec LayoutMainMenu
 </script>
 
 <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 {
   display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
   align-items: center;
   height: 48px;
   vertical-align: center;
@@ -116,25 +155,18 @@ onUnmounted(() => {
 :deep(.v-list-item__prepend) {
   margin: 10px 0;
   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) {
   max-width: 24px;
   margin-right: 10px;
 }
-
-.cancel-btn {
-  height: 42px;
-  color: rgb(var(--v-theme-on-neutral-very-soft));
-  background-color: transparent;
-  width: 100%;
-  border-top: solid 1px rgb(var(--v-theme-on-neutral-very-soft));
-  display: flex;
-  flex-direction: row;
-  justify-content: flex-start;
-}
-
-:deep(.cancel-btn .v-btn__prepend) {
-  margin: 0 16px 4px 2px;
-}
 </style>

+ 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>
   <div class="container">
+    <div class="d-flex flex-row mb-2">
+      <h4 v-if="title" class="align-self-center">
+        {{ title }}
+      </h4>
+    </div>
+
     <v-table>
       <thead>
         <tr>
           <td v-for="col in columns">
             {{ col.label }}
           </td>
-          <td>{{ i18n.t('actions') }}</td>
+          <td>{{ $t('actions') }}</td>
         </tr>
       </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] }}
           </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>
         </tr>
       </tbody>
       <tbody v-else>
-        <tr class="theme-neutral">
+        <tr>
           <td>
             <i>{{ i18n.t('nothing_to_show') }}</i>
           </td>
@@ -46,11 +80,11 @@ A data table for the parameters page
         </tr>
       </tbody>
     </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
-        :flat="true"
         prepend-icon="fa fa-plus"
-        class="theme-primary mt-4"
+        class="theme-neutral"
         @click="emit('addClicked')"
       >
         {{ i18n.t('add') }}
@@ -60,9 +94,9 @@ A data table for the parameters page
 </template>
 
 <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({
   /**
@@ -72,6 +106,12 @@ const props = defineProps({
     type: Array as PropType<Array<object>>,
     required: true,
   },
+  /** Titre du tableau */
+  title: {
+    type: String,
+    required: false,
+    default: null
+  },
   /**
    * If provided, define the columns to show.
    * Else, all the entity's props are shown.
@@ -84,7 +124,7 @@ const props = defineProps({
   columnsDefinitions: {
     type: Array as PropType<Array<ColumnDefinition> | null>,
     required: false,
-    default: null,
+    default: null
   },
   /**
    * The property used as identifier (required by 'edition' link)
@@ -92,7 +132,7 @@ const props = defineProps({
   identifier: {
     type: String,
     required: false,
-    default: 'id',
+    default: 'id'
   },
   /**
    * List of the actions available for each record
@@ -100,7 +140,7 @@ const props = defineProps({
   actions: {
     type: Array as PropType<Array<TABLE_ACTION>>,
     required: false,
-    default: [TABLE_ACTION.EDIT, TABLE_ACTION.DELETE, TABLE_ACTION.ADD],
+    default: [TABLE_ACTION.EDIT, TABLE_ACTION.DELETE, TABLE_ACTION.ADD]
   },
   /**
    * The URL for the edit / create pages
@@ -112,43 +152,47 @@ const props = defineProps({
   actionsRoute: {
     type: String,
     required: false,
-    default: '/parameters',
-  },
+    default: '/parameters'
+  }
 })
 
 const i18n = useI18n()
 
 const emit = defineEmits(['editClicked', 'deleteClicked', 'addClicked'])
 
+const { smAndUp, xs } = useDisplay()
+
 const getId = (item: object) => {
   return item[props.identifier]
 }
 
 const columns: ComputedRef<Array<ColumnDefinition>> = computed(() => {
-  return props.columnsDefinitions.map((col) => {
+  return props.columnsDefinitions.map(col => {
     return {
       property: col.property,
-      label: col.label ?? i18n.t(col.property),
+      label: col.label ?? i18n.t(col.property)
     }
   })
 })
+
 </script>
 
 <style scoped lang="scss">
+
 .container {
-  max-width: 1000px;
+  //max-width: 1000px;
+  //margin: 0 auto;
+  display: inline-block;
+  min-width: 65%;
 }
 
 .v-table {
-  width: 100%;
-
   thead {
     color: rgb(var(--v-theme-neutral-strong));
     font-weight: 600;
 
     td {
-      border-bottom: thin solid
-        rgba(var(--v-border-color), var(--v-border-opacity));
+      border-bottom: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
     }
 
     td:last-of-type {
@@ -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;
     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) {
-  color: rgb(var(--v-theme-neutral-strong));
+  color: rgb(var(--v-theme-on-neutral));
   font-size: 18px;
 }
 </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 {
     font-size: 14px;
     width: 55px !important;
-    padding: 0 !important;
+    padding: 0 2px 0 6px !important;
     margin-top: 0 !important;
     min-height: 24px;
     height: 24px;

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

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

+ 1 - 0
components/Layout/Subheader.vue

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

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

@@ -1,6 +1,6 @@
 <template>
   <v-btn
-    class="mr-4 theme-primary"
+    class="submit-btn theme-primary"
     :class="hasOtherActions ? 'pr-0' : ''"
     @click="submitAction(mainAction)"
     ref="mainBtn"
@@ -88,6 +88,15 @@ const hasOtherActions: ComputedRef<boolean> = computed(() => {
 </script>
 
 <style scoped>
+
+.submit-btn {
+  margin-right: 12px;
+
+  @media (max-width: 600px) {
+    margin: 0;
+  }
+}
+
 .v-list-item--dense {
   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>
-  <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"
-        :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>
 
 <script setup lang="ts">
-import { ref, computed, nextTick, watch, type PropType } from 'vue'
 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({
-  modelValue: Date,
-  label: {
-    type: String,
-    default: '',
-  },
-  format: {
-    type: String,
+  modelValue: {
+    type: Object as PropType<Date | Array<Date> | null>,
+    required: false,
     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 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>
 
-<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>

+ 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-row>
       </v-container>
-      <div v-else class="mt-12" />
+      <div v-else class="mt-6" />
 
       <!-- Content -->
       <slot v-bind="{ modelValue }" />
@@ -43,7 +43,7 @@ de quitter si des données ont été modifiées.
       <v-container
         v-if="actionPosition === 'both' || actionPosition === 'bottom'"
         :fluid="true"
-        class="container btnActions mt-6"
+        class="container btnActions"
       >
         <v-row>
           <v-col cols="12" sm="12">
@@ -410,6 +410,22 @@ defineExpose({ validate })
 <style scoped>
 .btnActions {
   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 {

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

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

+ 2 - 0
components/Ui/Image.vue

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

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

@@ -27,15 +27,18 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
       "
       :rules="rules"
       :chips="chips"
+      :closable-chips="closableChips"
       :hide-no-data="hideNoData"
       :no-data-text="
         isLoading ? $t('please_wait') : $t('no_result_matching_your_request')
       "
       :variant="variant"
+      density="compact"
       @update:model-value="onUpdate"
       @update:search="emit('update:search', $event)"
       @update:menu="emit('update:menu', $event)"
       @update:focused="emit('update:focused', $event)"
+      class="mb-3"
     >
       <template v-if="slotText" #item="data">
         <!--        <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>-->
@@ -144,6 +147,14 @@ const props = defineProps({
     type: Boolean,
     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
    */
@@ -359,3 +370,10 @@ const prepareItem = (item: Object): AnyJson => {
   })
 }
 </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"
       hide-no-data
       :chips="chips"
+      :closable-chips="true"
       :auto-select-first="false"
       prepend-inner-icon="fas fa-magnifying-glass"
       :return-object="false"
       :variant="variant"
       @update:model-value="onUpdateModelValue"
       @update:search="onUpdateSearch"
+      :class="(pending || pageStore.loading) ? 'hide-selection' : ''"
     />
   </main>
 </template>
@@ -139,6 +141,7 @@ interface UserListItem {
 
 const { fetchCollection } = useEntityFetch()
 const i18n = useI18n()
+const pageStore = usePageStore()
 
 /**
  * Génère un AccessListItem à partir d'un Access
@@ -257,4 +260,14 @@ const onUpdateModelValue = (event: Array<number>) => {
 .v-autocomplete {
   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>

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

@@ -9,9 +9,10 @@ Case à cocher, à placer dans un composant `UiForm`
     :model-value="modelValue"
     :label="$t(fieldLabel)"
     :disabled="readonly"
+    density="compact"
     :error="error || !!fieldViolations"
     :error-messages="errorMessage || fieldViolations ? $t(fieldViolations) : ''"
-    class="py-1"
+    class="checkbox py-1"
     @update:model-value="onUpdate"
   />
 </template>
@@ -92,4 +93,21 @@ const onUpdate = (event: boolean) => {
 }
 </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)"
       :items="items"
       :disabled="readonly"
+      density="compact"
       :error="error || !!fieldViolations"
       :error-messages="
         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>
   <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
-        v-model="date"
+        :model-value="date"
         :readonly="readonly"
-        :format="format"
-        :position="position"
+        class="date-picker"
         @update:model-value="onUpdate($event)"
       />
 
@@ -63,15 +64,6 @@ const props = defineProps({
     type: Boolean,
     required: false,
   },
-  /**
-   * Format d'affichage des dates
-   * @see https://vue3datepicker.com/props/formatting/
-   */
-  format: {
-    type: String,
-    required: false,
-    default: null,
-  },
   /**
    * Règles de validation
    * @see https://vuetify.cn/en/components/forms/#validation-with-submit-clear
@@ -96,14 +88,6 @@ const props = defineProps({
     required: false,
     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)
@@ -117,9 +101,51 @@ const date: Ref<Date | undefined> = ref(
 )
 
 const onUpdate = (event: string) => {
+  console.log(event)
   updateViolationState(event)
+  date.value = event ? new Date(event) : undefined
   emit('update:model-value', date.value ? formatISO(date.value) : undefined)
 }
 </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 { useValidationUtils } from '~/composables/utils/useValidationUtils'
 import type { PropType } from '@vue/runtime-core'
+import { useI18n } from 'vue-i18n'
 
 const props = defineProps({
   label: {
@@ -74,7 +75,9 @@ const props = defineProps({
   },
 })
 
-const { emit, i18n } = useNuxtApp()
+const { emit } = useNuxtApp()
+
+const i18n = useI18n()
 
 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="
         errorMessage || (fieldViolations ? $t(fieldViolations) : '')
       "
+      density="compact"
       @update:modelValue="
         updateViolationState($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 class="upload__cropper-wrapper">
               <Cropper
+                v-if="croppingEnabled"
                 ref="cropper"
                 class="upload__cropper"
                 check-orientation
@@ -43,6 +44,12 @@ Assistant de création d'image
                 @change="onCropperChange"
               />
 
+              <v-img
+                v-else
+                :src="currentImage.src ?? ''"
+                class="upload__cropper"
+              />
+
               <div
                 v-if="currentImage.src"
                 class="upload__reset-button"
@@ -107,8 +114,7 @@ const props = defineProps({
     default: null,
   },
   /**
-   * Label du champ
-   * Si non défini, c'est le nom de propriété qui est utilisé
+   * Nom du champ
    */
   field: {
     type: String,
@@ -136,6 +142,14 @@ const props = defineProps({
     type: Number,
     required: false,
   },
+  /**
+   * Donne la possibilité de rogner les images
+   */
+  croppingEnabled: {
+    type: Boolean,
+    required: false,
+    default: true,
+  },
   /**
    * TODO: completer
    */

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

@@ -9,9 +9,9 @@ An input for numeric values
     :model-value.number="modelValue"
     :label="label || field ? $t(label ?? field) : undefined"
     hide-details
-    :density="density"
     type="number"
     :variant="variant"
+    density="compact"
     @update:model-value="onModelUpdate($event)"
   />
 </template>
@@ -60,11 +60,6 @@ const props = defineProps({
     required: false,
     default: null,
   },
-  density: {
-    type: String as PropType<Density>,
-    required: false,
-    default: 'default',
-  },
   /**
    * @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') : ''"
     :variant="variant"
+    density="compact"
     @click:append="show = !show"
     @update:model-value="onUpdate($event)"
     @change="onChange($event)"

+ 15 - 26
components/Ui/SystemBar.vue

@@ -3,23 +3,20 @@ System bars
 -->
 
 <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"
   >
-    <!-- Forcing z-index to avoid this : https://github.com/vuetifyjs/nuxt-module/issues/205 -->
     <slot>
       <v-icon v-if="icon" small :icon="icon" />
       {{ text }}
     </slot>
-  </v-system-bar>
+  </div>
 </template>
 
 <script setup lang="ts">
+import {useDisplay} from 'vuetify';
+
 const props = defineProps({
   text: {
     type: String,
@@ -31,34 +28,26 @@ const props = defineProps({
     required: false,
     default: undefined,
   },
-  backgroundColor: {
-    type: String,
-    required: false,
-    default: 'neutral-soft',
-  },
-  textColor: {
-    type: String,
-    required: false,
-    default: 'on-neutral-soft',
-  },
   onClick: {
     type: Function,
     required: false,
     default: undefined,
   },
 })
-
-// TODO: voir si possible d'utiliser les variables sass à la place?
-const classes = [
-  'bg-' + props.backgroundColor,
-  'text-' + props.textColor,
-  props.onClick !== undefined ? 'clickable' : '',
-].join(' ')
 </script>
 
+
 <style scoped lang="scss">
-.v-system-bar {
+.alert-bar {
+  position: relative;
   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 {

+ 8 - 4
composables/utils/useRedirect.ts

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

+ 46 - 26
i18n/lang/fr.json

@@ -195,37 +195,44 @@
   "OTHER": "Autre",
   "CONTACT": "Contact",
   "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",
   "username": "Nom d'utilisateur",
   "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",
   "passwordSMS": "Mot de passe SMS",
   "usernameSMS": "Nom d'utilisateur SMS",
-  "smsSenderName": "Personnaliser le nom de l'expéditeur SMS",
+  "smsSenderName": "Nom d'expéditeur SMS personnalisé",
   "attendance": "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",
   "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",
   "sendAttendanceSms": "Prévenir automatiquement la famille par sms en cas d'absence non justifiée",
   "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",
+  "itemsToDisplayOnBulletins": "Eléments à afficher sur les bulletins",
+  "bulletinSettings": "Configuration",
   "superAdminEmail": "Adresse mail associée",
   "bulletin_parameters": "Bulletins",
   "sms": "Sms",
@@ -243,7 +250,7 @@
   "qrCode": "QrCode",
   "qrCodeForLicence": "QrCode pour la licence",
   "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 ",
   "startCourseDate": "Date de début des cours ",
   "generalParams": "Paramètres généraux",
@@ -505,6 +512,7 @@
   "advanced_modification": "Administration site internet",
   "simple_modification": "Modifications simplifiées",
   "create": "Créer",
+  "edit": "Modifier",
   "help_access": "Accès aide",
   "configuration": "Configuration",
   "organization_page": "Fiche de la structure",
@@ -514,6 +522,7 @@
   "cmf_licence_details_url": "Consulter les avantages de la licence CMF",
   "generate": "Générer",
   "parameters": "Préférences",
+  "parameters_page": "Préférences",
   "places": "Lieux",
   "education": "Enseignements",
   "tags": "Tags",
@@ -632,7 +641,7 @@
   "start_date_of_activity_season": "Début de saison d'activité",
   "start_date_of_courses": "Date de début 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",
   "parameters_general_page": "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_sms_page": "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",
   "super_admin": "Compte super-admin",
   "parameters_super_admin_page": "Compte super-admin",
   "super_admin_breadcrumbs": "Compte super-admin",
   "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_short": "Nouveau sous-domaine",
   "your_subdomains": "Vos sous-domaines",
+  "other_website": "Autre site internet",
   "Not Found": "Données non trouvée",
   "subdomains_breadcrumbs": "Sous-domaines",
   "new_breadcrumbs": "Nouveau",
@@ -671,10 +686,11 @@
   "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 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",
   "unknown": "Inconnu",
   "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_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",
@@ -687,11 +703,11 @@
   "OUT_CYCLE": "Hors cycle",
   "originalLabel": "Libellés d'origine",
   "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é",
   "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",
   "max_note_for_pedagogical_followup": "Note maximale pour les notes du suivi pédagogique (entre 1 et 100) ",
   "Bad Request": "Requête invalide",
@@ -704,6 +720,7 @@
   "education_timings_breadcrumbs": "Durée des cours",
   "create_a_new_residence_area": "Créer une nouvelle zone 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.",
   "exit": "Quitter",
   "max_size_4_mb": "Taille maximum: 4 MO",
@@ -720,9 +737,12 @@
   "id": "Id",
   "missing_name": "Nom manquant",
   "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.",
-  "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",
   "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.",

+ 1 - 1
layouts/default.vue

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

+ 18 - 8
layouts/parameters.vue

@@ -13,12 +13,14 @@
       <v-main class="main">
         <LayoutSubheader />
 
-        <LayoutAlertBar class="mt-1" />
+        <LayoutAlertBar />
 
         <!-- Page will be rendered here-->
-        <v-card class="parameters-page-card">
+        <div class="inner-container">
+          <h3>{{ pageTitle }}</h3>
+
           <slot />
-        </v-card>
+        </div>
       </v-main>
 
       <LazyLayoutAlertContainer />
@@ -31,13 +33,21 @@ import { useLayoutStore } from '~/stores/layout'
 
 const layoutStore = useLayoutStore()
 layoutStore.name = 'parameters'
+
+const route = useRoute()
+const i18n = useI18n()
+
+const pageTitle = computed(() => i18n.t(route.name || 'parameters_page'))
+
 </script>
 
 <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>

+ 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: {
     strict: true,

+ 1 - 1
package.json

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

+ 1 - 0
pages/parameters.vue

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

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

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

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

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

+ 35 - 31
pages/parameters/attendances.vue

@@ -1,43 +1,47 @@
 <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
       :model="AttendanceBookingReason"
+      :title="$t('attendanceBookingReasons')"
       :columns-definitions="[{ property: 'reason' }]"
     />
-  </LayoutContainer>
+  </LayoutParametersSection>
 </template>
 <script setup lang="ts">
 import type { AsyncData } from '#app'

+ 72 - 63
pages/parameters/bulletin.vue

@@ -1,68 +1,77 @@
 <template>
   <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>
 </template>
 

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

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

+ 47 - 42
pages/parameters/education_notation.vue

@@ -1,52 +1,57 @@
 <template>
   <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>
 </template>
 

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

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

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

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

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

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

+ 76 - 72
pages/parameters/general_parameters.vue

@@ -1,84 +1,88 @@
 <template>
   <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>
 </template>
 

+ 45 - 39
pages/parameters/intranet.vue

@@ -1,49 +1,55 @@
 <template>
   <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>
 </template>
 

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

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

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

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

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

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

+ 26 - 28
pages/parameters/sms.vue

@@ -1,33 +1,29 @@
 <template>
   <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>
 </template>
 <script setup lang="ts">
@@ -66,6 +62,7 @@ const rules = () => [
 </script>
 
 <style scoped lang="scss">
+
 /**
 Simule une apparence de saisie de type mot de passe
 Sans ça, les navigateurs proposent la saisie semi auto et la mémorisation du mot de passe
@@ -77,6 +74,7 @@ Sans ça, les navigateurs proposent la saisie semi auto et la mémorisation du m
   font-display: block;
   src: url(https://jsbin-user-assets.s3.amazonaws.com/rafaelcastrocouto/password.ttf);
 }
+
 :deep(.password input) {
   font-family: 'password';
 }

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

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

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

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

+ 37 - 42
pages/parameters/super_admin.vue

@@ -1,40 +1,40 @@
 <template>
   <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 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>
 </template>
 
@@ -42,7 +42,6 @@
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useAccessProfileStore } from '~/stores/accessProfile'
 import AdminAccess from '~/models/Access/AdminAccess'
-import { useValidationUtils } from '~/composables/utils/useValidationUtils'
 
 definePageMeta({
   name: 'parameters_super_admin_page',
@@ -56,15 +55,6 @@ if (accessProfile.id === null) {
 }
 
 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>
 
 <style scoped lang="scss">
@@ -87,6 +77,11 @@ const rules = () => [
     height: 28px;
     width: 28px;
   }
+
+  @media (max-width: 600px) {
+    flex-direction: column;
+    justify-content: center;
+  }
 }
 
 .v-table td:first-child {

+ 30 - 24
pages/parameters/teaching.vue

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

+ 105 - 139
pages/parameters/website.vue

@@ -1,136 +1,101 @@
 <template>
   <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">
               <a :href="organizationProfile.website ?? '#'" target="_blank">
                 {{ organizationProfile.website }}
               </a>
             </strong>
           </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>
 </template>
 
@@ -144,6 +109,7 @@ import Subdomain from '~/models/Organization/Subdomain'
 import ApiResource from '~/models/ApiResource'
 import EqualFilter from '~/services/data/Filters/EqualFilter'
 import Query from '~/services/data/Query'
+import {useDisplay} from 'vuetify';
 
 definePageMeta({
   name: 'parameters_website_page',
@@ -153,6 +119,8 @@ const { fetch, fetchCollection } = useEntityFetch()
 
 const organizationProfile = useOrganizationProfileStore()
 
+const { smAndUp } = useDisplay()
+
 if (organizationProfile.parametersId === null) {
   throw new Error('Missing organization parameters id')
 }
@@ -184,26 +152,24 @@ const onAddSubdomainClick = () => {
   }
   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 {

+ 1 - 1
services/data/enumManager.ts

@@ -23,7 +23,7 @@ class EnumManager {
     const enum_: Enum = []
     for (const key in data.items) {
       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
     }
 
-    const imageUrl = `api/download/${id}`
+    const imageUrl = `api/file/download/${id}`
 
     // Set requested size if needed
     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
     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)

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

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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Répertoire
  */
 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

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

@@ -6,7 +6,7 @@ import AbstractMenuBuilder from '~/services/layout/menuBuilder/abstractMenuBuild
  * Menu Mon compte
  */
 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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Admin 2IOS
  */
 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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu agenda
  */
 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

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

@@ -6,7 +6,7 @@ import AbstractMenuBuilder from '~/services/layout/menuBuilder/abstractMenuBuild
  * Menu Basicompta
  */
 export default class BasicomptaMenuBuilder extends AbstractMenuBuilder {
-  static readonly menuName = 'Basicompta'
+  static override readonly menuName = 'Basicompta'
 
   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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Facturation
  */
 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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Communication
  */
 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

+ 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
  */
 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
@@ -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')) {
       children.push(

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Cotisation (CMF)
  */
 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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Donneurs
  */
 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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Suivi pédagogique
  */
 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

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

@@ -6,7 +6,7 @@ import AbstractMenuBuilder from '~/services/layout/menuBuilder/abstractMenuBuild
  * Menu Matériel
  */
 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

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

@@ -18,7 +18,7 @@ import BasicomptaMenuBuilder from '~/services/layout/menuBuilder/basicomptaMenuB
  * Menu principal (ou menu lateral)
  */
 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

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

@@ -7,7 +7,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Mon Profil
  */
 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

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

@@ -7,7 +7,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Famille
  */
 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

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

@@ -5,7 +5,7 @@ import type { MenuGroup, MenuItems } from '~/types/layout'
  * Menu Paramètres
  */
 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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Distinctions
  */
 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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Statistiques
  */
 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

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

@@ -6,7 +6,7 @@ import { MENU_LINK_TYPE } from '~/types/enum/layout'
  * Menu Site internet
  */
 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

+ 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
  */
 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

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 301 - 258
yarn.lock


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů