Olivier Massot 2 лет назад
Родитель
Сommit
411046ac32
100 измененных файлов с 927 добавлено и 719 удалено
  1. 1 1
      .gitlab-ci.yml
  2. 1 0
      .nuxtignore
  3. 1 1
      .nvmrc
  4. 12 0
      assets/css/global.scss
  5. 2 2
      components/Layout/Alert/Container.vue
  6. 2 2
      components/Layout/Alert/Content.vue
  7. 1 1
      components/Layout/AlertBar/Cotisation.vue
  8. 1 1
      components/Layout/AlertBar/RegistrationStatus.vue
  9. 1 1
      components/Layout/AlertBar/SuperAdmin.vue
  10. 1 1
      components/Layout/AlertBar/SwitchYear.vue
  11. 0 1
      components/Layout/Dialog.vue
  12. 23 4
      components/Layout/Header.vue
  13. 2 5
      components/Layout/Header/HomeBtn.vue
  14. 3 2
      components/Layout/Header/Notification.vue
  15. 3 3
      components/Layout/Header/UniversalCreation/Card.vue
  16. 3 2
      components/Layout/Header/UniversalCreation/CreateButton.vue
  17. 3 2
      components/Layout/Header/UniversalCreation/EventParams.vue
  18. 2 3
      components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue
  19. 2 3
      components/Layout/MainMenu.vue
  20. 0 101
      components/Layout/Parameters/EducationTimings.vue
  21. 0 95
      components/Layout/Parameters/ResidenceAreas.vue
  22. 131 0
      components/Layout/ParametersMenu.vue
  23. 3 2
      components/Layout/SubHeader/Breadcrumbs.vue
  24. 1 1
      components/Layout/SubHeader/DataTiming.vue
  25. 1 1
      components/Layout/SubHeader/DataTimingRange.vue
  26. 3 2
      components/Layout/SubHeader/PersonnalizedList.vue
  27. 5 4
      components/Layout/Subheader.vue
  28. 1 1
      components/Layout/ThemeSwitcher.vue
  29. 1 1
      components/Ui/Button/Delete.vue
  30. 2 1
      components/Ui/Button/Submit.vue
  31. 3 2
      components/Ui/Collection.vue
  32. 3 2
      components/Ui/DataTable.vue
  33. 10 1
      components/Ui/DatePicker.vue
  34. 1 1
      components/Ui/DateRangePicker.vue
  35. 21 6
      components/Ui/Form.vue
  36. 3 3
      components/Ui/Form/Creation.vue
  37. 3 3
      components/Ui/Form/Edition.vue
  38. 1 1
      components/Ui/Help.vue
  39. 4 2
      components/Ui/Image.vue
  40. 13 3
      components/Ui/Input/Autocomplete.vue
  41. 17 3
      components/Ui/Input/Autocomplete/Accesses.vue
  42. 2 2
      components/Ui/Input/AutocompleteWithAPI.vue
  43. 4 3
      components/Ui/Input/AutocompleteWithAp2i.vue
  44. 13 3
      components/Ui/Input/AutocompleteWithEnum.vue
  45. 9 13
      components/Ui/Input/Checkbox.vue
  46. 11 1
      components/Ui/Input/DatePicker.vue
  47. 5 3
      components/Ui/Input/Image.vue
  48. 10 1
      components/Ui/Input/Number.vue
  49. 1 1
      components/Ui/Input/Phone.vue
  50. 13 4
      components/Ui/Input/Text.vue
  51. 2 3
      components/Ui/ItemFromUri.vue
  52. 2 1
      components/Ui/Template/Date.vue
  53. 2 1
      components/Ui/Xeditable/Text.vue
  54. 3 3
      composables/data/useAp2iRequestService.ts
  55. 3 3
      composables/data/useEntityFetch.ts
  56. 2 2
      composables/data/useEnumFetch.ts
  57. 1 1
      composables/data/useImageFetch.ts
  58. 2 1
      composables/form/useFieldViolation.ts
  59. 1 1
      composables/form/useValidation.ts
  60. 1 1
      composables/layout/useExtensionPanel.ts
  61. 4 4
      composables/layout/useMenu.ts
  62. 10 0
      composables/utils/useHomeUrl.ts
  63. 10 3
      config/theme.ts
  64. 11 1
      lang/fr.json
  65. 3 19
      layouts/default.vue
  66. 4 0
      layouts/error.vue
  67. 46 0
      layouts/parameters.vue
  68. 1 1
      models/Access/Access.ts
  69. 1 1
      models/Access/MyProfile.ts
  70. 1 1
      models/Billing/ResidenceArea.ts
  71. 0 1
      models/OnlineRegistration/RegistrationStatus.ts
  72. 5 1
      nuxt.config.ts
  73. 44 44
      package.json
  74. 1 2
      pages/cmf_licence/organization.vue
  75. 2 1
      pages/organization/index.vue
  76. 9 2
      pages/parameters.vue
  77. 9 3
      pages/parameters/attendances.vue
  78. 4 4
      pages/parameters/bulletin.vue
  79. 15 15
      pages/parameters/cycles/[id].vue
  80. 0 10
      pages/parameters/cycles/index.vue
  81. 7 4
      pages/parameters/education_notation.vue
  82. 2 4
      pages/parameters/education_timings/[id].vue
  83. 95 5
      pages/parameters/education_timings/index.vue
  84. 1 3
      pages/parameters/education_timings/new.vue
  85. 8 6
      pages/parameters/general_parameters.vue
  86. 9 131
      pages/parameters/index.vue
  87. 3 4
      pages/parameters/intranet.vue
  88. 1 5
      pages/parameters/residence_areas/[id].vue
  89. 92 5
      pages/parameters/residence_areas/index.vue
  90. 1 3
      pages/parameters/residence_areas/new.vue
  91. 31 5
      pages/parameters/sms.vue
  92. 1 1
      pages/parameters/subdomains/[id].vue
  93. 0 10
      pages/parameters/subdomains/index.vue
  94. 4 6
      pages/parameters/subdomains/new.vue
  95. 37 41
      pages/parameters/super_admin.vue
  96. 13 14
      pages/parameters/teaching.vue
  97. 60 37
      pages/parameters/website.vue
  98. 2 1
      pages/poc/[id].vue
  99. 2 1
      pages/poc/index.vue
  100. 0 1
      pages/poc/new.vue

+ 1 - 1
.gitlab-ci.yml

@@ -16,7 +16,7 @@ unit:
   stage: test
 
   script:
-    - HOST=ci yarn install
+    - HOST=ci yarn install --network-timeout 10000
     - yarn test
 
   artifacts:

+ 1 - 0
.nuxtignore

@@ -0,0 +1 @@
+coverage

+ 1 - 1
.nvmrc

@@ -1 +1 @@
-18.10
+18.19

Разница между файлами не показана из-за своего большого размера
+ 12 - 0
assets/css/global.scss


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

@@ -16,9 +16,9 @@ Container principal pour l'affichage d'une ou plusieurs alertes
 </template>
 
 <script setup lang="ts">
-import { Alert } from '~/types/interfaces'
+import type { Alert } from '~/types/interfaces'
 import {usePageStore} from "~/stores/page";
-import {ComputedRef} from "@vue/reactivity";
+import type {ComputedRef} from "@vue/reactivity";
 
 const pageStore = usePageStore()
 

+ 2 - 2
components/Layout/Alert/Content.vue

@@ -24,8 +24,8 @@
 </template>
 
 <script setup lang="ts">
-import {Alert} from '~/types/interfaces'
-import {Ref} from "@vue/reactivity";
+import type {Alert} from '~/types/interfaces'
+import type {Ref} from "@vue/reactivity";
 import {usePageStore} from "~/stores/page";
 
 const props = defineProps({

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

@@ -18,7 +18,7 @@ Barre d'alerte qui s'affiche pour donner l'état de la cotisation
 
 <script setup lang="ts">
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import {Ref} from "vue";
+import type {Ref} from "vue";
 import UrlUtils from "~/services/utils/urlUtils";
 import {ALERT_STATE_COTISATION} from "~/types/enum/enums";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";

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

@@ -16,7 +16,7 @@ Barre d'alerte quand au statut (l'avancement) de l'inscription en ligne de l'uti
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {useAccessProfileStore} from "~/stores/accessProfile";
 import RegistrationStatus from "~/models/OnlineRegistration/RegistrationStatus";
-import {ComputedRef} from "@vue/reactivity";
+import type {ComputedRef} from "@vue/reactivity";
 
 const { fetch } = useEntityFetch()
 

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

@@ -15,7 +15,7 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur est un super admin en mode sw
 <script setup lang="ts">
   import {useAccessProfileStore} from "~/stores/accessProfile";
   import UrlUtils from "~/services/utils/urlUtils";
-  import {ComputedRef} from "@vue/reactivity";
+  import type {ComputedRef} from "@vue/reactivity";
 
   const runtimeConfig = useRuntimeConfig()
 

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

@@ -20,7 +20,7 @@ Barre d'alerte qui s'affiche lorsque l'utilisateur n'est pas sur l'année couran
 <script setup lang="ts">
   import {useAccessProfileStore} from "~/stores/accessProfile";
   import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-  import {ComputedRef} from "@vue/reactivity";
+  import type {ComputedRef} from "@vue/reactivity";
   import {useFormStore} from "~/stores/form";
   import Access from "~/models/Access/Access";
   import {usePageStore} from "~/stores/page";

+ 0 - 1
components/Layout/Dialog.vue

@@ -34,7 +34,6 @@
 </template>
 
 <script setup lang="ts">
-import {PropType} from "@vue/runtime-core";
 
 const props = defineProps({
   show: {

+ 23 - 4
components/Layout/Header.vue

@@ -11,10 +11,21 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
   >
     <template #prepend>
       <v-app-bar-nav-icon
-          v-if="hasMainMenu"
+          v-if="hasMainMenu && layoutStore.name !== 'parameters'"
           :icon="isMainMenuOpened ? 'mdi:mdi-menu-open' : 'mdi:mdi-menu'"
           @click="toggleMainMenu"
       />
+      <div v-else-if="hasParametersMenu && layoutStore.name === 'parameters'">
+        <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"/>
@@ -39,7 +50,7 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
 
     <a
         :href="runtimeConfig.supportUrl"
-        class="text-body pa-3 ml-2 theme-secondary text-decoration-none h-100"
+        class="text-body px-3 py-4 ml-2 theme-secondary text-decoration-none h-100"
         target="_blank"
     >
       <span class="d-none d-sm-none d-md-flex">{{ $t('help_access') }}</span>
@@ -50,11 +61,13 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
 
 <script setup lang="ts">
 
-import {computed, ComputedRef} from "@vue/reactivity";
+import {computed} from "@vue/reactivity";
+import type {ComputedRef} from "@vue/reactivity";
 import {useMenu} from "~/composables/layout/useMenu";
 import {useAbility} from "@casl/vue";
-import {useDisplay, useTheme} from 'vuetify'
+import {useDisplay} from 'vuetify'
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
+import {useLayoutStore} from "~/stores/layout";
 
 const organizationProfile = useOrganizationProfileStore()
 const runtimeConfig = useRuntimeConfig()
@@ -68,6 +81,10 @@ 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 ability = useAbility()
 const showUniversalButton =
     ability.can('manage', 'users')
@@ -81,6 +98,8 @@ const showUniversalButton =
     || ability.can('display', 'message_send_page')
     || ability.can('manage', 'equipments')
 
+const layoutStore = useLayoutStore()
+
 </script>
 
 <style scoped>

+ 2 - 5
components/Layout/Header/HomeBtn.vue

@@ -14,12 +14,9 @@
 <script setup lang="ts">
   import {ref} from "@vue/reactivity";
   import {useDisplay} from "vuetify";
-  import UrlUtils from "~/services/utils/urlUtils";
+  import {useHomeUrl} from "~/composables/utils/useHomeUrl";
 
-  const runtimeConfig = useRuntimeConfig()
-  const homeUrl = UrlUtils.join(runtimeConfig.baseUrlAdminLegacy, '#', 'dashboard')
-
-  const { mdAndUp } = useDisplay()
+  const { homeUrl } = useHomeUrl()
 
   const btn = ref(null);
 </script>

+ 3 - 2
components/Layout/Header/Notification.vue

@@ -96,9 +96,10 @@ import {NOTIFICATION_TYPE} from "~/types/enum/enums";
 import Notification from "~/models/Core/Notification";
 import NotificationUsers from "~/models/Core/NotificationUsers";
 import {useAccessProfileStore} from "~/stores/accessProfile";
-import {computed, ComputedRef, Ref, ref} from "@vue/reactivity";
+import {computed, ref} from "@vue/reactivity";
+import type {ComputedRef, Ref} from "@vue/reactivity";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {AnyJson, Pagination} from "~/types/data";
+import type {AnyJson, Pagination} from "~/types/data";
 import {useEntityManager} from "~/composables/data/useEntityManager";
 import UrlUtils from "~/services/utils/urlUtils";
 import {useRepo} from "pinia-orm";

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

@@ -12,11 +12,11 @@
   <v-card
     class="col-md-6"
     color=""
-    flat
+    :flat="true"
     border="solid 1px"
     @click="onClick"
   >
-    <v-row no-gutters style="height: 100px">
+    <v-row :no-gutters="true" style="height: 100px">
       <v-col cols="3" class="flex-grow-0 flex-shrink-0 d-flex justify-center">
         <v-icon
             :icon="icon"
@@ -39,7 +39,7 @@
 </template>
 
 <script setup lang="ts">
-  import {PropType} from "@vue/runtime-core";
+  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";

+ 3 - 2
components/Layout/Header/UniversalCreation/CreateButton.vue

@@ -64,9 +64,10 @@
 </template>
 
 <script setup lang="ts">
-  import {Ref, ref} from "@vue/reactivity";
+  import {ref} from "@vue/reactivity";
+  import type {Ref} from "@vue/reactivity";
   import {useDisplay} from "vuetify";
-  import {ComputedRef} from "vue";
+  import type {ComputedRef} from "vue";
   import {usePageStore} from "~/stores/page";
 
   const { mdAndDown: asIcon } = useDisplay()

+ 3 - 2
components/Layout/Header/UniversalCreation/EventParams.vue

@@ -52,9 +52,10 @@ Event parameters page in the create dialog
 </template>
 
 <script setup lang="ts">
-  import {ref, Ref} from "@vue/reactivity";
+  import {ref} from "@vue/reactivity";
+  import type {Ref} from "@vue/reactivity";
   import {add, format, startOfHour, formatISO} from "date-fns";
-  import {ComputedRef} from "vue";
+  import type {ComputedRef} from "vue";
   import DateUtils, {supportedLocales} from "~/services/utils/dateUtils";
 
   const i18n = useI18n()

+ 2 - 3
components/Layout/Header/UniversalCreation/GenerateCardsSteps.vue

@@ -280,11 +280,10 @@
 </template>
 
 <script setup lang="ts">
-  import {Ref, ref} from "@vue/reactivity";
+  import type {Ref} from "@vue/reactivity";
   import {useOrganizationProfileStore} from "~/stores/organizationProfile";
   import {useAbility} from "@casl/vue";
-  import {ComputedRef} from "vue";
-  import {useAdminUrl} from "~/composables/utils/useAdminUrl";
+  import type {ComputedRef} from "vue";
   import UrlUtils from "~/services/utils/urlUtils";
 
   const props = defineProps({

+ 2 - 3
components/Layout/MainMenu.vue

@@ -76,7 +76,7 @@ Prend en paramètre une liste de ItemMenu et les met en forme
 import {useMenu} from "~/composables/layout/useMenu";
 import {computed} from "@vue/reactivity";
 import { useDisplay } from 'vuetify'
-import { MenuGroup, MenuItem } from "~/types/layout";
+import type { MenuGroup, MenuItem } from "~/types/layout";
 
 const { getMenu, hasMenu, isInternalLink, setMenuState, isMenuOpened } = useMenu()
 
@@ -124,8 +124,7 @@ onUnmounted(() => {
     min-height: 10px !important;
   }
 
-  :deep(.v-list-item-title),
-  :deep(.v-icon),
+  :deep(.v-list-item-title), :deep(.v-icon)
   {
     font-size: 14px;
     color: rgb(var(--v-theme-on-secondary));

+ 0 - 101
components/Layout/Parameters/EducationTimings.vue

@@ -1,101 +0,0 @@
-<template>
-  <LayoutContainer>
-    <UiLoadingPanel v-if="pending" />
-    <v-container v-else style="width: 500px;">
-      <v-col cols="12">
-        <v-row class="justify-center">
-          <v-table class="w-100">
-            <thead>
-              <tr>
-                <td>{{ $t('educationTimings') }}</td>
-                <td></td>
-              </tr>
-            </thead>
-            <tbody>
-              <tr v-if="educationTimings.length > 0" v-for="timing in educationTimings" :key="timing.id">
-                <td class="cycle-editable-cell">
-                  {{ timing.timing }}
-                </td>
-                <td class="d-flex flex-row">
-                  <v-btn
-                      :flat="true"
-                      icon="fa fa-pen"
-                      class="cycle-edit-icon mr-3"
-                      @click="goToEditPage(timing.id as number)"
-                  />
-                  <UiButtonDelete
-                      :model="EducationTiming"
-                      :entity="timing"
-                      :flat="true"
-                      class="cycle-edit-icon"
-                  />
-                </td>
-              </tr>
-              <tr v-else class="theme-neutral">
-                <td><i>{{ $t('nothing_to_show')}}</i></td>
-                <td></td>
-              </tr>
-            </tbody>
-          </v-table>
-        </v-row>
-        <v-row class="justify-end">
-          <v-btn
-              :flat="true"
-              prepend-icon="fa fa-plus"
-              class="theme-primary mt-2"
-              @click="goToCreatePage"
-          >
-            {{ $t('add') }}
-          </v-btn>
-        </v-row>
-      </v-col>
-    </v-container>
-  </LayoutContainer>
-
-</template>
-
-<script setup lang="ts">
-import { useEntityFetch } from '~/composables/data/useEntityFetch'
-import EducationTiming from '~/models/Education/EducationTiming'
-import { useRepo } from 'pinia-orm'
-import EducationTimingsRepository from '~/stores/repositories/EducationTimingsRepository'
-import {ComputedRef} from "vue";
-import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import UrlUtils from "~/services/utils/urlUtils";
-
-const organizationProfile = useOrganizationProfileStore()
-
-if (organizationProfile.parametersId === null) {
-  throw new Error('Missing organization parameters id')
-}
-
-const { fetch, fetchCollection } = useEntityFetch()
-
-const { pending } = fetchCollection(EducationTiming)
-
-const educationTimingRepo = useRepo(EducationTimingsRepository)
-
-/**
- * On récupère les timings via le store
- * (sans ça, les mises à jour SSE ne seront pas prises en compte)
- */
-const educationTimings: ComputedRef<Array<EducationTiming>> = computed(() => {
-  return educationTimingRepo.getEducationTimings()
-})
-
-const goToEditPage = (id: number) => {
-  navigateTo(UrlUtils.join('/parameters/education_timings', id))
-}
-
-const goToCreatePage = () => {
-  navigateTo('/parameters/education_timings/new')
-}
-</script>
-
-<style scoped lang="scss">
-// TODO: voir à factoriser ces styles, ptêt en faisant un component de ces boutons?
-:deep(.cycle-edit-icon .v-icon) {
-  color: rgb(var(--v-theme-primary));
-  font-size: 18px;
-}
-</style>

+ 0 - 95
components/Layout/Parameters/ResidenceAreas.vue

@@ -1,95 +0,0 @@
-<template>
-  <LayoutContainer>
-    <UiLoadingPanel v-if="pending" />
-    <v-container v-else style="width: 500px;">
-      <v-col cols="12">
-        <v-row class="justify-center">
-          <v-table class="w-100">
-            <thead>
-            <tr>
-              <td>{{ $t('residenceAreas') }}</td>
-              <td></td>
-            </tr>
-            </thead>
-            <tbody>
-            <tr v-if="residenceAreas.length > 0" v-for="residenceArea in residenceAreas" :key="residenceArea.id">
-              <td class="cycle-editable-cell">
-                {{ residenceArea.label }}
-              </td>
-              <td class="d-flex flex-row">
-                <v-btn
-                    :flat="true"
-                    icon="fa fa-pen"
-                    class="cycle-edit-icon mr-3"
-                    @click="goToEditPage(residenceArea.id as number)"
-                />
-                <UiButtonDelete
-                    :model="ResidenceArea"
-                    :entity="residenceArea"
-                    :flat="true"
-                    class="cycle-edit-icon"
-                />
-              </td>
-            </tr>
-            <tr v-else class="theme-neutral">
-              <td><i>{{ $t('nothing_to_show')}}</i></td>
-              <td></td>
-            </tr>
-            </tbody>
-          </v-table>
-        </v-row>
-        <v-row class="justify-end">
-          <v-btn
-              :flat="true"
-              prepend-icon="fa fa-plus"
-              class="theme-primary mt-2"
-              @click="goToCreatePage"
-          >
-            {{ $t('add') }}
-          </v-btn>
-        </v-row>
-      </v-col>
-    </v-container>
-  </LayoutContainer>
-</template>
-
-<script setup lang="ts">
-import { useEntityFetch } from '~/composables/data/useEntityFetch'
-import ResidenceArea from '~/models/Billing/ResidenceArea'
-import { useRepo } from 'pinia-orm'
-import ResidenceAreasRepository from '~/stores/repositories/ResidenceAreasRepository'
-import { useRouter } from 'vue-router'
-import UrlUtils from "~/services/utils/urlUtils";
-import EducationTiming from "~/models/Education/EducationTiming";
-const residenceAreasRepo = useRepo(ResidenceAreasRepository)
-
-const router = useRouter()
-const { fetchCollection } = useEntityFetch()
-const i18n = useI18n()
-
-const { pending } = fetchCollection(ResidenceArea)
-
-/**
- * On récupère les Residence Area via le store
- * (sans ça, les mises à jour SSE ne seront pas prises en compte)
- */
- const residenceAreas: ComputedRef<Array<ResidenceArea>> = computed(() => {
-  return residenceAreasRepo.getResidenceAreas()
-})
-
-const goToEditPage = (id: number) => {
-  navigateTo(UrlUtils.join('/parameters/residence_areas', id))
-}
-
-const goToCreatePage = () => {
-  navigateTo(`/parameters/residence_areas/new`)
-}
-</script>
-
-<style scoped lang="scss">
-// TODO: voir à factoriser ces styles, ptêt en faisant un component de ces boutons?
-:deep(.cycle-edit-icon .v-icon) {
-  color: rgb(var(--v-theme-primary));
-  font-size: 18px;
-}
-</style>

+ 131 - 0
components/Layout/ParametersMenu.vue

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

+ 3 - 2
components/Layout/SubHeader/Breadcrumbs.vue

@@ -5,8 +5,9 @@
 </template>
 
 <script setup lang="ts">
-import {computed, ComputedRef} from "@vue/reactivity";
-import {AnyJson} from "~/types/data";
+import {computed} from "@vue/reactivity";
+import type {ComputedRef} from "@vue/reactivity";
+import type {AnyJson} from "~/types/data";
 import {useI18n} from "vue-i18n";
 import UrlUtils from "~/services/utils/urlUtils";
 

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

@@ -31,7 +31,7 @@
 <script setup lang="ts">
 import {useFormStore} from "~/stores/form";
 import {useAccessProfileStore} from "~/stores/accessProfile";
-import {Ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import {useEntityManager} from "~/composables/data/useEntityManager";
 import {useDisplay, useTheme} from "vuetify";
 import Access from "~/models/Access/Access";

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

@@ -15,7 +15,7 @@
 </template>
 
 <script setup lang="ts">
-import {Ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import {useAccessProfileStore} from "~/stores/accessProfile";
 import {useFormStore} from "~/stores/form";
 import {useEntityManager} from "~/composables/data/useEntityManager";

+ 3 - 2
components/Layout/SubHeader/PersonnalizedList.vue

@@ -49,8 +49,9 @@
 <script setup lang="ts">
 import PersonalizedList from '~/models/Access/PersonalizedList'
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {ComputedRef, Ref, ref} from "@vue/reactivity";
-import {AnyJson} from "~/types/data";
+import {ref} from "@vue/reactivity";
+import type {ComputedRef, Ref} from "@vue/reactivity";
+import type {AnyJson} from "~/types/data";
 import ApiResource from "~/models/ApiResource";
 import UrlUtils from "~/services/utils/urlUtils";
 

+ 5 - 4
components/Layout/Subheader.vue

@@ -8,7 +8,7 @@ Contient entre autres le breadcrumb, les commandes de changement d'année et les
     <v-card
       id="subheader"
       class="d-flex theme-neutral text-body-2 px-2"
-      flat
+      :flat="true"
       rounded="0"
     >
       <LayoutSubHeaderBreadcrumbs v-if="lgAndUp" class="mr-auto d-flex" />
@@ -17,7 +17,7 @@ Contient entre autres le breadcrumb, les commandes de changement d'année et les
 
       <v-card
         class="d-flex flex-row align-center mr-6"
-        flat
+        :flat="true"
         tile
       >
         <LayoutSubHeaderActivityYear v-if="smAndUp && !showDateTimeRange" class="activity-year" />
@@ -57,11 +57,12 @@ Contient entre autres le breadcrumb, les commandes de changement d'année et les
 
 <script setup lang="ts">
     import {useAccessProfileStore} from "~/stores/accessProfile";
-    import {computed, ComputedRef, ref, Ref} from "@vue/reactivity";
+    import {computed, ref} from "@vue/reactivity";
+    import type {ComputedRef, Ref} from "@vue/reactivity";
     import {useMenu} from "~/composables/layout/useMenu";
     import {useDisplay} from "vuetify";
 
-    const { smAndUp, mdAndUp, lgAndUp } = useDisplay()
+    const { smAndUp, lgAndUp } = useDisplay()
     const accessProfile = useAccessProfileStore()
     const { hasMenu } = useMenu()
     const btn: Ref = ref(null)

+ 1 - 1
components/Layout/ThemeSwitcher.vue

@@ -2,7 +2,7 @@
   <v-switch
       v-model="theme.global.name.value"
       density="compact"
-      inline
+      :inline="true"
       false-value="light"
       false-icon="fas fa-sun"
       true-value="dark"

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

@@ -32,7 +32,7 @@ Bouton Delete avec modale de confirmation de la suppression
 
 <script setup lang="ts">
 import {TYPE_ALERT} from '~/types/enum/enums'
-import {Ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import {useEntityManager} from "~/composables/data/useEntityManager";
 import ApiResource from "~/models/ApiResource";
 import {usePageStore} from "~/stores/page";

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

@@ -44,7 +44,8 @@
 </template>
 
 <script setup lang="ts">
-import {computed, ComputedRef, ref, Ref} from "@vue/reactivity";
+import {computed, ref} from "@vue/reactivity";
+import type {ComputedRef, Ref} from "@vue/reactivity";
 
 const props = defineProps({
   actions: {

+ 3 - 2
components/Ui/Collection.vue

@@ -22,9 +22,10 @@
 
 <script setup lang="ts">
 
-import {computed, ComputedRef, toRefs, ToRefs} from "@vue/reactivity";
+import {computed, toRefs} from "@vue/reactivity";
+import type {ComputedRef, ToRefs} from "@vue/reactivity";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {Collection} from "~/types/data";
+import type {Collection} from "~/types/data";
 
 const props = defineProps({
   model: {

+ 3 - 2
components/Ui/DataTable.vue

@@ -43,10 +43,11 @@ Tableau interactif conçu pour l'affichage d'une collection d'entités
 
 <script setup lang="ts">
 
-import {ref, Ref, toRefs} from "@vue/reactivity";
+import {ref, toRefs} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import ApiResource from "~/models/ApiResource";
-import {AnyJson} from "~/types/data";
+import type {AnyJson} from "~/types/data";
 
 const props = defineProps({
   parent: {

+ 10 - 1
components/Ui/DatePicker.vue

@@ -18,6 +18,7 @@ Sélecteur de dates
         :select-text="$t('select')"
         :cancel-text="$t('cancel')"
         :disabled="readonly"
+        :position="position"
         @update:model-value="onUpdate"
     />
   </main>
@@ -25,7 +26,7 @@ Sélecteur de dates
 
 <script setup lang="ts">
 import DateUtils, {supportedLocales} from "~/services/utils/dateUtils";
-import {PropType} from "@vue/runtime-core";
+import type {PropType} from "@vue/runtime-core";
 
 const i18n = useI18n()
 
@@ -51,6 +52,14 @@ const props = defineProps({
     type: Boolean,
     required: false,
     default: false
+  },
+  /**
+   * @see https://vue3datepicker.com/props/positioning/#position
+   */
+  position: {
+    type: String as PropType<'left' | 'center' | 'right'>,
+    required: false,
+    default: 'center'
   }
 })
 

+ 1 - 1
components/Ui/DateRangePicker.vue

@@ -25,7 +25,7 @@
 
 <script setup lang="ts">
 import DateUtils, {supportedLocales} from "~/services/utils/dateUtils";
-import {PropType} from "@vue/runtime-core";
+import type {PropType} from "@vue/runtime-core";
 
 const props = defineProps({
   modelValue: {

+ 21 - 6
components/Ui/Form.vue

@@ -16,7 +16,11 @@ de quitter si des données ont été modifiées.
         @submit.prevent=""
     >
       <!-- Top action bar -->
-      <v-container :fluid="true" class="container btnActions">
+      <v-container
+          v-if="actionPosition === 'both' || actionPosition === 'top'"
+          :fluid="true"
+          class="container btnActions"
+      >
         <v-row>
           <v-col cols="12" sm="12">
             <slot name="form.button"/>
@@ -35,7 +39,11 @@ de quitter si des données ont été modifiées.
       <slot v-bind="{model, entity}"/>
 
       <!-- Bottom action bar -->
-      <v-container :fluid="true" class="container btnActions">
+      <v-container
+          v-if="actionPosition === 'both' || actionPosition === 'bottom'"
+          :fluid="true"
+          class="container btnActions mt-6"
+      >
         <v-row>
           <v-col cols="12" sm="12">
             <slot name="form.button"/>
@@ -80,15 +88,17 @@ de quitter si des données ont été modifiées.
 </template>
 
 <script setup lang="ts">
-import {computed, ComputedRef, ref, Ref} from "@vue/reactivity";
+import {computed, ref} from "@vue/reactivity";
+import type {ComputedRef, Ref} from "@vue/reactivity";
 import {FORM_FUNCTION, SUBMIT_TYPE, TYPE_ALERT} from "~/types/enum/enums";
 import { useFormStore } from "~/stores/form";
-import {Route, RouteLocationRaw} from "@intlify/vue-router-bridge";
+import type {Route, RouteLocationRaw} from "@intlify/vue-router-bridge";
 import {useEntityManager} from "~/composables/data/useEntityManager";
 import ApiModel from "~/models/ApiModel";
 import {usePageStore} from "~/stores/page";
-import {PropType, watch} from "@vue/runtime-core";
-import {AnyJson} from "~/types/data";
+import {watch} from "@vue/runtime-core";
+import type {PropType} from "@vue/runtime-core";
+import type {AnyJson} from "~/types/data";
 import * as _ from 'lodash-es'
 import {useRefreshProfile} from "~/composables/data/useRefreshProfile";
 
@@ -146,6 +156,11 @@ const props = defineProps({
     type: Boolean,
     required: false,
     default: false
+  },
+  actionPosition: {
+    type: String as PropType<'top' | 'bottom' | 'both'>,
+    required: false,
+    default: 'both'
   }
 })
 

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

@@ -16,10 +16,10 @@
 
 <script setup lang="ts">
 
-import {PropType} from "@vue/runtime-core";
-import {RouteLocationRaw} from "@intlify/vue-router-bridge";
+import type {PropType} from "@vue/runtime-core";
+import type {RouteLocationRaw} from "@intlify/vue-router-bridge";
 import ApiModel from "~/models/ApiModel";
-import {AnyJson} from "~/types/data";
+import type {AnyJson} from "~/types/data";
 import {SUBMIT_TYPE} from "~/types/enum/enums";
 import {useEntityManager} from "~/composables/data/useEntityManager";
 

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

@@ -20,10 +20,10 @@
 
 <script setup lang="ts">
 
-import {PropType} from "@vue/runtime-core";
-import {RouteLocationRaw} from "@intlify/vue-router-bridge";
+import type {PropType} from "@vue/runtime-core";
+import type {RouteLocationRaw} from "@intlify/vue-router-bridge";
 import ApiModel from "~/models/ApiModel";
-import {AnyJson} from "~/types/data";
+import type {AnyJson} from "~/types/data";
 import {SUBMIT_TYPE} from "~/types/enum/enums";
 import {useRoute} from "vue-router";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";

+ 1 - 1
components/Ui/Help.vue

@@ -29,7 +29,7 @@
 </template>
 
 <script setup lang="ts">
-import {Ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 
 const props = defineProps({
   left: {

+ 4 - 2
components/Ui/Image.vue

@@ -48,9 +48,11 @@ Si la propriété 'upload' est à 'true', propose aussi un input pour uploader u
 
 
 <script setup lang="ts">
-import {ref, Ref} from "@vue/reactivity";
+import {ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import {useImageFetch} from "~/composables/data/useImageFetch";
-import {onUnmounted, watch, WatchStopHandle} from "@vue/runtime-core";
+import {onUnmounted, watch} from "@vue/runtime-core";
+import type {WatchStopHandle} from "@vue/runtime-core";
 import {useImageManager} from "~/composables/data/useImageManager";
 import ImageManager from "~/services/data/imageManager";
 

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

@@ -27,6 +27,7 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
         :chips="chips"
         :hide-no-data="hideNoData"
         :no-data-text="isLoading ? $t('please_wait') : $t('no_result_matching_your_request')"
+        :variant="variant"
         @update:model-value="onUpdate"
         @update:search="emit('update:search', $event)"
         @update:menu="emit('update:menu', $event)"
@@ -40,11 +41,12 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
 </template>
 
 <script setup lang="ts">
-import {computed, ComputedRef, Ref} from "@vue/reactivity";
+import {computed} from "@vue/reactivity";
+import type {ComputedRef, Ref} from "@vue/reactivity";
 import {useFieldViolation} from "~/composables/form/useFieldViolation";
 import ObjectUtils from "~/services/utils/objectUtils";
-import {AnyJson} from "~/types/data";
-import {PropType} from "@vue/runtime-core";
+import type {AnyJson} from "~/types/data";
+import type {PropType} from "@vue/runtime-core";
 
 const props = defineProps({
   /**
@@ -210,6 +212,14 @@ const props = defineProps({
     type: String,
     required: false,
     default: null
+  },
+  /**
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
+   */
+  variant: {
+    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    required: false,
+    default: 'filled'
   }
 })
 

+ 17 - 3
components/Ui/Input/Autocomplete/Accesses.vue

@@ -19,6 +19,7 @@ Champs autocomplete dédié à la recherche des access d'une structure
         :auto-select-first="false"
         prependIcon="fas fa-magnifying-glass"
         :return-object="false"
+        :variant="variant"
         @update:model-value="onUpdateModelValue"
         @update:search="onUpdateSearch"
     />
@@ -26,9 +27,10 @@ Champs autocomplete dédié à la recherche des access d'une structure
 </template>
 
 <script setup lang="ts">
-import {PropType} from "@vue/runtime-core";
-import {computed, ComputedRef, Ref} from "@vue/reactivity";
-import {AnyJson, AssociativeArray} from "~/types/data";
+import type {PropType} from "@vue/runtime-core";
+import {computed} from "@vue/reactivity";
+import type {ComputedRef, Ref} from "@vue/reactivity";
+import type {AnyJson, AssociativeArray} from "~/types/data";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import Access from "~/models/Access/Access";
 import {useEntityManager} from "~/composables/data/useEntityManager";
@@ -101,6 +103,14 @@ const props = defineProps({
   clearSearchAfterUpdate: {
     type: Boolean,
     default: false
+  },
+  /**
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
+   */
+  variant: {
+    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    required: false,
+    default: 'filled'
   }
 })
 
@@ -179,6 +189,10 @@ const items: ComputedRef<Array<AccessListItem>> = computed(() => {
     return []
   }
 
+  if (!collection.value) {
+    return []
+  }
+
   //@ts-ignore
   const fetchedItems = collection.value.items.map(accessToItem)
 

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

@@ -28,9 +28,9 @@ d'une api)
 
 <script setup lang="ts">
 
-import {Ref, ref, toRefs} from "@vue/reactivity";
+import {ref, toRefs} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import UrlUtils from "~/services/utils/urlUtils";
-import {FetchOptions} from "ohmyfetch";
 import {useFetch} from "#app";
 import {watch} from "@vue/runtime-core";
 

+ 4 - 3
components/Ui/Input/AutocompleteWithAp2i.vue

@@ -23,12 +23,13 @@ Liste déroulante avec autocompletion issue de Ap2i
 
 <script setup lang="ts">
 
-import {computed, ComputedRef, Ref} from "@vue/reactivity";
-import {PropType} from "@vue/runtime-core";
+import {computed} from "@vue/reactivity";
+import type {ComputedRef, Ref} from "@vue/reactivity";
+import type {PropType} from "@vue/runtime-core";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import ApiResource from "~/models/ApiResource";
 import ApiModel from "~/models/ApiModel";
-import {AnyJson, AssociativeArray, Collection} from "~/types/data";
+import type {AnyJson, AssociativeArray} from "~/types/data";
 
 const props = defineProps({
   /**

+ 13 - 3
components/Ui/Input/AutocompleteWithEnum.vue

@@ -8,6 +8,7 @@
       :return-object="false"
       item-title="label"
       item-value="value"
+      :variant="variant"
       @update:model-value="$emit('update:model-value', $event)"
   />
 </template>
@@ -17,12 +18,13 @@
 
 import {useEnumFetch} from "~/composables/data/useEnumFetch";
 import ArrayUtils from "~/services/utils/arrayUtils";
-import {ComputedRef} from "@vue/reactivity";
-import {Enum} from "~/types/data";
+import type {ComputedRef} from "@vue/reactivity";
+import type {Enum} from "~/types/data";
+import type {PropType} from "@vue/runtime-core";
 
 const props = defineProps({
   modelValue: {
-    type: String,
+    type: String as PropType<string | null>,
     required: false,
     default: null
   },
@@ -39,6 +41,14 @@ const props = defineProps({
     type: String,
     required: false,
     default: null
+  },
+  /**
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
+   */
+  variant: {
+    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    required: false,
+    default: 'filled'
   }
 })
 

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

@@ -5,19 +5,15 @@ Case à cocher, à placer dans un composant `UiForm`
 -->
 
 <template>
-  <v-container
-    class="px-0"
-    :fluid="true"
-  >
-    <v-checkbox
-      :model-value="modelValue"
-      :label="$t(fieldLabel)"
-      :disabled="readonly"
-      :error="error || !!fieldViolations"
-      :error-messages="errorMessage || fieldViolations ? $t(fieldViolations) : ''"
-      @update:model-value="onUpdate"
-    />
-  </v-container>
+  <v-checkbox
+    :model-value="modelValue"
+    :label="$t(fieldLabel)"
+    :disabled="readonly"
+    :error="error || !!fieldViolations"
+    :error-messages="errorMessage || fieldViolations ? $t(fieldViolations) : ''"
+    class="py-1"
+    @update:model-value="onUpdate"
+  />
 </template>
 
 <script setup lang="ts">

+ 11 - 1
components/Ui/Input/DatePicker.vue

@@ -11,6 +11,7 @@ Sélecteur de dates, à placer dans un composant `UiForm`
           v-model="date"
           :readonly="readonly"
           :format="format"
+          :position="position"
           @update:model-value="onUpdate($event)"
       />
 
@@ -24,13 +25,14 @@ Sélecteur de dates, à placer dans un composant `UiForm`
 <script setup lang="ts">
 import {useFieldViolation} from "~/composables/form/useFieldViolation";
 import {formatISO} from "date-fns";
+import type {PropType} from "@vue/runtime-core";
 
 const props = defineProps({
   /**
    * v-model
    */
   modelValue: {
-    type: String,
+    type: String as PropType<string | null>,
     required: false,
     default: null
   },
@@ -92,6 +94,14 @@ const props = defineProps({
     type: String,
     required: false,
     default: null
+  },
+  /**
+   * @see https://vue3datepicker.com/props/positioning/#position
+   */
+  position: {
+    type: String as PropType<'left' | 'center' | 'right'>,
+    required: false,
+    default: 'center'
   }
 })
 

+ 5 - 3
components/Ui/Input/Image.vue

@@ -60,14 +60,16 @@ https://norserium.github.io/vue-advanced-cropper/
 <script setup lang="ts">
 
 import {useNuxtApp} from "#app";
-import {ref, Ref} from "@vue/reactivity";
+import {ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import File from '~/models/Core/File'
 import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
 import UrlUtils from "~/services/utils/urlUtils";
 import {useImageFetch} from "~/composables/data/useImageFetch";
-import {onUnmounted, watch, WatchStopHandle} from "@vue/runtime-core";
+import {onUnmounted, watch} from "@vue/runtime-core";
+import type {WatchStopHandle} from "@vue/runtime-core";
 import {useEntityManager} from "~/composables/data/useEntityManager";
-import {AnyJson} from "~/types/data";
+import type {AnyJson} from "~/types/data";
 
 const props = defineProps({
   imageId: {

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

@@ -10,13 +10,14 @@ An input for numeric values
       hide-details
       :density="density"
       type="number"
+      :variant="variant"
       @update:modelValue="modelValue = keepInRange(cast($event)); emitUpdate()"
   />
 </template>
 
 <script setup lang="ts">
 
-import {PropType} from "@vue/runtime-core";
+import type {PropType} from "@vue/runtime-core";
 
 type Density = null | 'default' | 'comfortable' | 'compact';
 
@@ -62,6 +63,14 @@ const props = defineProps({
     type: String as PropType<Density>,
     required: false,
     default: 'default'
+  },
+  /**
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
+   */
+  variant: {
+    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    required: false,
+    default: 'filled'
   }
 })
 

+ 1 - 1
components/Ui/Input/Phone.vue

@@ -29,7 +29,7 @@ Champs de saisie d'un numéro de téléphone
 
 import {useNuxtApp} from "#app";
 import {useFieldViolation} from "~/composables/form/useFieldViolation";
-import {Ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 
 const props = defineProps({
   label: {

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

@@ -15,6 +15,7 @@ Champs de saisie de texte, à placer dans un composant `UiForm`
     :error="error || !!fieldViolations"
     :error-messages="errorMessage || (fieldViolations ? $t(fieldViolations) : '')"
     :append-icon="type === 'password' ? (show ? 'mdi-eye' : 'mdi-eye-off') : ''"
+    :variant="variant"
     @click:append="show = !show"
     @update:model-value="onUpdate($event)"
     @change="onChange($event)"
@@ -25,16 +26,16 @@ Champs de saisie de texte, à placer dans un composant `UiForm`
 </template>
 
 <script setup lang="ts">
-import {ref} from "@vue/reactivity";
+import {type Ref, ref} from "@vue/reactivity";
 import {useFieldViolation} from "~/composables/form/useFieldViolation";
-import {PropType} from "@vue/runtime-core";
+import type {PropType} from "@vue/runtime-core";
 
 const props = defineProps({
   /**
    * v-model
    */
   modelValue: {
-    type: [String, Number],
+    type: [String, Number] as PropType<string | number | null>,
     required: false,
     default: null
   },
@@ -107,6 +108,14 @@ const props = defineProps({
     type: [Array, Boolean],
     required: false,
     default: false
+  },
+  /**
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-variant
+   */
+  variant: {
+    type: String as PropType<"filled" | "outlined" | "plain" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>,
+    required: false,
+    default: 'filled'
   }
 })
 
@@ -114,7 +123,7 @@ const input = ref(null)
 
 const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
 
-const show = ref(false)
+const show: Ref<boolean> = ref(false)
 
 const emit = defineEmits(['update:model-value', 'change'])
 

+ 2 - 3
components/Ui/ItemFromUri.vue

@@ -20,9 +20,8 @@ Espace permettant de récupérer un item via une uri et de gérer son affichage
 import {Query} from "pinia-orm";
 import UrlUtils from "~/services/utils/urlUtils";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {object} from "@ucast/core";
-import {computed, ComputedRef} from "@vue/reactivity";
-import {schema} from "@pinia-orm/normalizr";
+import {computed} from "@vue/reactivity";
+import type {ComputedRef} from "@vue/reactivity";
 import ApiResource from "~/models/ApiResource";
 
 const props = defineProps({

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

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

+ 2 - 1
components/Ui/Xeditable/Text.vue

@@ -38,7 +38,8 @@ Utilisé par exemple pour le choix de l'année active
 </template>
 
 <script setup lang="ts">
-import {ref, Ref} from "@vue/reactivity";
+import {ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 
   const props = defineProps({
     type: {

+ 3 - 3
composables/data/useAp2iRequestService.ts

@@ -1,11 +1,11 @@
 import {TYPE_ALERT} from "~/types/enum/enums";
 import ApiRequestService from "~/services/data/apiRequestService";
-import {Ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import {usePageStore} from "~/stores/page";
 import UnauthorizedError from "~/services/error/UnauthorizedError";
 import {useAccessProfileStore} from "~/stores/accessProfile";
-import {AssociativeArray} from "~/types/data";
-import {FetchContext, FetchOptions} from "ofetch";
+import type {AssociativeArray} from "~/types/data";
+import type {FetchContext, FetchOptions} from "ofetch";
 
 /**
  * Retourne une instance de ApiRequestService configurée pour interroger l'api Ap2i

+ 3 - 3
composables/data/useEntityFetch.ts

@@ -1,8 +1,8 @@
 import {useEntityManager} from "~/composables/data/useEntityManager";
 import ApiResource from "~/models/ApiResource";
-import {AssociativeArray, Collection} from "~/types/data";
-import {AsyncData} from "#app";
-import {ComputedRef, Ref} from "vue";
+import type {AssociativeArray, Collection} from "~/types/data";
+import type {AsyncData} from "#app";
+import type {ComputedRef, Ref} from "vue";
 import {v4 as uuid4} from "uuid";
 
 interface useEntityFetchReturnType {

+ 2 - 2
composables/data/useEnumFetch.ts

@@ -1,6 +1,6 @@
 import {useEnumManager} from "~/composables/data/useEnumManager";
-import {Enum} from "~/types/data";
-import {AsyncData} from "#app";
+import type {Enum} from "~/types/data";
+import type {AsyncData} from "#app";
 
 interface useEnumFetchReturnType {
     fetch: (enumName: string) => AsyncData<Enum, null | true | Error>,

+ 1 - 1
composables/data/useImageFetch.ts

@@ -1,5 +1,5 @@
 import {useImageManager} from "~/composables/data/useImageManager";
-import {FetchResult} from "#app";
+import type {FetchResult} from "#app";
 
 interface useImageFetchReturnType {
     fetch: (id: number | null, defaultImage?: string | null, height?: number, width?: number) => FetchResult<any, any>

+ 2 - 1
composables/form/useFieldViolation.ts

@@ -1,4 +1,5 @@
-import {computed, ComputedRef} from "@vue/reactivity";
+import {computed} from "@vue/reactivity";
+import type {ComputedRef} from "@vue/reactivity";
 import {useFormStore} from "~/stores/form";
 import * as _ from 'lodash-es'
 

+ 1 - 1
composables/form/useValidation.ts

@@ -1,7 +1,7 @@
 import  {useI18n} from 'vue-i18n'
 import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
 import UrlUtils from "~/services/utils/urlUtils";
-import {Ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 
 /**
  * @category composables/form

+ 1 - 1
composables/layout/useExtensionPanel.ts

@@ -1,4 +1,4 @@
-import {Ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import * as _ from 'lodash-es'
 
 /**

+ 4 - 4
composables/layout/useMenu.ts

@@ -1,9 +1,9 @@
 import {useAccessProfileStore} from "~/stores/accessProfile";
 import {useAbility} from "@casl/vue";
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import {MenuGroup, MenuItem} from "~/types/layout";
+import type {MenuGroup, MenuItem} from "~/types/layout";
 import {MENU_LINK_TYPE} from "~/types/enum/layout";
-import {AccessProfile} from "~/types/interfaces";
+import type {AccessProfile} from "~/types/interfaces";
 import {useLayoutStore} from "~/stores/layout";
 import MenuComposer from "~/services/layout/menuComposer";
 
@@ -28,7 +28,7 @@ export const useMenu = () => {
    * Construct all Menus
    * TODO: ce serait mieux de conserver les ids des menus même non possédés, de façon à pouvoir différencier un menu
    * non possédé et un id incorrect dans getMenu par exemple. J'ai eu du mal capter pourquoi hasMenu('Family') renvoyait
-   * false, jusqu'à ce que je tilte que le menu s'appellait MyFamily, et pas Family
+   * false, jusqu'à ce que je tilte que le menu s'appelait MyFamily, et pas Family
    */
   const buildAllMenu = () => {
     MenuComposer.build(runtimeConfig, ability, organizationProfile, accessProfile as AccessProfile, layoutState)
@@ -130,6 +130,6 @@ export const useMenu = () => {
     closeMenu,
     toggleMenu,
     isMenuOpened,
-    isInternalLink
+    isInternalLink,
   }
 }

+ 10 - 0
composables/utils/useHomeUrl.ts

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

+ 10 - 3
config/theme.ts

@@ -1,4 +1,3 @@
-import { ThemeDefinition } from 'vuetify'
 
 interface Theme {
     dark: boolean,
@@ -26,6 +25,8 @@ interface Theme {
         'on-neutral--clickable': string,
         'neutral-soft': string,
         'on-neutral-soft': string,
+        'neutral-very-soft': string,
+        'on-neutral-very-soft': string,
         'danger': string,
         'on-danger': string,
         'success': string,
@@ -71,8 +72,11 @@ export const lightTheme: Theme = {
         'on-neutral': '#666666',
         'on-neutral--clickable': '#00997d',
 
-        'neutral-soft': '#ecf0f5',
-        'on-neutral-soft': '#464646',
+        'neutral-soft': '#f2f2f2',
+        'on-neutral-soft': '#333333',
+
+        'neutral-very-soft': '#ffffff',
+        'on-neutral-very-soft': '#333333',
 
         // Content
         'danger': '#f56954',
@@ -123,6 +127,9 @@ export const darkTheme: Theme = {
         'neutral-soft': '#090c11',
         'on-neutral-soft': '#cccccc',
 
+        'neutral-very-soft': '#333333',
+        'on-neutral-very-soft': '#ecf0f5',
+
         // Content
         'danger': '#f56954',
         'on-danger': '#ffffff',

+ 11 - 1
lang/fr.json

@@ -404,6 +404,7 @@
   "network": "Réseau",
   "schedule": "Agenda",
   "attendances": "Absences",
+  "attendances_breadcrumbs": "Absences",
   "equipment": "Parc matériel",
   "education_state": "Suivi pédagogique",
   "criteria_notations": "Critères d'évaluation",
@@ -467,6 +468,7 @@
   "right_menu": "Droits version 5.9",
   "tree_menu": "Gestion de l'arbre",
   "website": "Site internet",
+  "website_breadcrumbs": "Site internet",
   "websiteList": "Site(s) internet",
   "advanced_modification": "Administration site internet",
   "simple_modification": "Modifications simplifiées",
@@ -602,15 +604,22 @@
   "show_adherents_list_and_their_coordinates": "Afficher la liste des adhérents et leurs coordonnées",
   "students_are_also_association_members": "Les élèves sont adhérents également de l'association",
   "general_parameters": "Paramètres généraux",
+  "general_parameters_breadcrumbs": "Paramètres généraux",
   "teaching": "Enseignements",
+  "teaching_breadcrumbs": "Enseignements",
   "intranet_access": "Accès intranet (professeurs, élèves...)",
+  "intranet_breadcrumbs": "Accès intranet",
   "educationNotations": "Suivi pédagogique",
+  "education_notation_breadcrumbs": "Suivi pédagogique",
   "bulletin": "Bulletins",
+  "bulletin_breadcrumbs": "Bulletins",
   "educationTimings": "Durée des cours (en minutes)",
   "new_education_timing": "Nouvelle durée de cours",
   "residenceAreas": "Zones de résidence",
   "sms_option": "Option SMS",
+  "sms_breadcrumbs": "SMS",
   "super_admin": "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",
   "record_a_new_subdomain": "Enregistrer un nouveau sous-domaine",
@@ -657,5 +666,6 @@
   "create_a_new_residence_area": "Créer une nouvelle zone de résidence",
   "residence_areas_breadcrumbs": "Zones 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.",
-  "cycles_breadcrumbs": "Enseignements"
+  "cycles_breadcrumbs": "Enseignements",
+  "exit": "Quitter"
 }

Разница между файлами не показана из-за своего большого размера
+ 3 - 19
layouts/default.vue


+ 4 - 0
layouts/error.vue

@@ -8,6 +8,10 @@
 
 <script setup lang="ts">
   import UrlUtils from "~/services/utils/urlUtils";
+  import {useLayoutStore} from "~/stores/layout";
+
+  const layoutStore = useLayoutStore()
+  layoutStore.name = 'error'
 
   const props = defineProps({
     error: {

+ 46 - 0
layouts/parameters.vue

@@ -0,0 +1,46 @@
+<template>
+  <div>
+    <!-- Show the loading page -->
+    <client-only placeholder-tag="client-only-placeholder" placeholder=" " />
+
+    <v-app>
+      <LayoutLoadingScreen />
+
+      <LayoutHeader />
+
+      <LayoutParametersMenu />
+
+      <v-main class="main">
+
+        <LayoutSubheader />
+
+        <LayoutAlertBar class="mt-1" />
+
+        <!-- Page will be rendered here-->
+        <v-card class="parameters-page-card">
+          <slot />
+        </v-card>
+      </v-main>
+
+      <LazyLayoutAlertContainer />
+
+    </v-app>
+  </div>
+</template>
+
+<script setup lang="ts">
+
+import {useLayoutStore} from "~/stores/layout";
+
+const layoutStore = useLayoutStore()
+layoutStore.name = 'parameters'
+</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;
+  }
+</style>

+ 1 - 1
models/Access/Access.ts

@@ -1,4 +1,4 @@
-import { Historical } from '~/types/interfaces'
+import type { Historical } from '~/types/interfaces'
 import Person from "~/models/Person/Person";
 import ApiModel from "~/models/ApiModel";
 import {HasOne, Num, Uid, Attr} from "pinia-orm/dist/decorators";

+ 1 - 1
models/Access/MyProfile.ts

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

+ 1 - 1
models/Billing/ResidenceArea.ts

@@ -10,7 +10,7 @@ export default class ResidenceArea extends ApiModel {
   static entity = 'residence_areas'
 
   @Uid()
-  declare id: number | string | null
+  declare id: number | string
 
   @Str(null)
   declare label: string|null

+ 0 - 1
models/OnlineRegistration/RegistrationStatus.ts

@@ -1,4 +1,3 @@
-import ApiModel from "~/models/ApiModel";
 import {Num, Str, Uid} from "pinia-orm/dist/decorators";
 import ApiResource from "~/models/ApiResource";
 

+ 5 - 1
nuxt.config.ts

@@ -1,6 +1,5 @@
 import fs from 'fs';
 import vuetify from 'vite-plugin-vuetify'
-import * as os from "os";
 
 let https = {}
 
@@ -108,6 +107,11 @@ export default defineNuxtConfig({
     vite: {
         esbuild: {
             drop: process.env.DEBUG ? [] : ['console', 'debugger'],
+            tsconfigRaw: {
+                compilerOptions: {
+                    experimentalDecorators: true,
+                }
+            }
         },
         ssr: {
             // with ssr enabled, this config is required to load vuetify properly

+ 44 - 44
package.json

@@ -16,67 +16,67 @@
     "nuxt-upgrade": "nuxi upgrade --force"
   },
   "dependencies": {
-    "@casl/ability": "^6.3.3",
+    "@casl/ability": "^6.5.0",
     "@casl/vue": "2.2.1",
-    "@fortawesome/fontawesome-free": "^6.3.0",
-    "@mdi/font": "^7.0.96",
-    "@nuxt/image": "rc",
-    "@nuxtjs/i18n": "^8.0.0-rc.5",
-    "@pinia-orm/nuxt": "^1.6.7",
-    "@pinia/nuxt": "0.4.11",
-    "@types/file-saver": "^2.0.5",
-    "@types/js-yaml": "^4.0.5",
-    "@types/vue-the-mask": "^0.11.1",
-    "@vitest/coverage-v8": "^0.32.4",
-    "@vuepic/vue-datepicker": "^5.3.0",
+    "@fortawesome/fontawesome-free": "^6.5.1",
+    "@mdi/font": "^7.3.67",
+    "@nuxt/image": "1.1.0",
+    "@nuxtjs/i18n": "^8.0.0-rc.8",
+    "@pinia-orm/nuxt": "^1.7.0",
+    "@pinia/nuxt": "0.5.1",
+    "@vuepic/vue-datepicker": "^7.4.0",
     "cleave.js": "^1.6.0",
-    "date-fns": "^2.29.3",
+    "date-fns": "^2.30.0",
     "event-source-polyfill": "^1.0.31",
     "file-saver": "^2.0.5",
     "js-yaml": "^4.1.0",
-    "libphonenumber-js": "1.10.36",
+    "libphonenumber-js": "1.10.51",
     "lodash": "^4.17.21",
     "lodash-es": "^4.17.21",
-    "nuxt": "^3.5.3",
-    "pinia": "^2.1.4",
-    "pinia-orm": "^1.6.7",
-    "sass": "^1.59.3",
-    "uuid": "^9.0.0",
+    "nuxt": "^3.8.2",
+    "pinia": "^2.1.7",
+    "pinia-orm": "^1.7.2",
+    "sass": "^1.69.5",
+    "uuid": "^9.0.1",
     "vite-plugin-vuetify": "^1.0.2",
     "vue-tel-input-vuetify": "^1.5.3",
     "vue-the-mask": "^0.11.1",
-    "vuetify": "3.3.5",
+    "vuetify": "3.4.6",
     "yaml-import": "^2.0.0"
   },
   "devDependencies": {
-    "@nuxt/devtools": "^0.6.2",
-    "@nuxt/test-utils": "^3.3.1",
-    "@nuxt/test-utils-edge": "3.6.0-28122147.c3c56c14",
+    "@nuxt/devtools": "^1.0.5",
+    "@nuxt/test-utils": "^3.8.1",
+    "@nuxt/test-utils-edge": "3.8.0-28284309.b3d3d7f4",
     "@nuxtjs/eslint-config": "^12.0.0",
-    "@nuxtjs/eslint-config-typescript": "^12.0.0",
-    "@nuxtjs/eslint-module": "^4.0.2",
-    "@types/cleave.js": "^1.4.7",
-    "@types/event-source-polyfill": "^1.0.1",
-    "@types/jest": "^29.4.2",
-    "@types/lodash": "^4.14.189",
-    "@types/lodash-es": "^4.17.7",
-    "@types/uuid": "^9.0.0",
-    "@typescript-eslint/eslint-plugin": "^5.55.0",
-    "@typescript-eslint/parser": "^5.55.0",
-    "@vitejs/plugin-vue": "^4.0.0",
+    "@nuxtjs/eslint-config-typescript": "^12.1.0",
+    "@nuxtjs/eslint-module": "^4.1.0",
+    "@types/cleave.js": "^1.4.12",
+    "@types/event-source-polyfill": "^1.0.5",
+    "@types/jest": "^29.5.11",
+    "@types/lodash": "^4.14.202",
+    "@types/lodash-es": "^4.17.12",
+    "@types/uuid": "^9.0.7",
+    "@types/file-saver": "^2.0.7",
+    "@types/js-yaml": "^4.0.9",
+    "@types/vue-the-mask": "^0.11.1",
+    "@typescript-eslint/eslint-plugin": "^6.13.2",
+    "@typescript-eslint/parser": "^6.13.2",
+    "@vitejs/plugin-vue": "^4.5.2",
+    "@vitest/coverage-v8": "^1.0.2",
     "@vue/eslint-config-standard": "^8.0.1",
-    "@vue/test-utils": "^2.3.1",
+    "@vue/test-utils": "^2.4.3",
     "blob-polyfill": "^7.0.20220408",
-    "eslint": "^8.36.0",
-    "eslint-config-prettier": "^8.7.0",
+    "eslint": "^8.55.0",
+    "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-nuxt": "^4.0.0",
-    "eslint-plugin-prettier": "^4.2.1",
-    "eslint-plugin-vue": "^9.7.0",
-    "jsdom": "^22.1.0",
-    "prettier": "^2.8.4",
-    "ts-jest": "^29.0.3",
-    "typescript": "^5.2",
-    "vitest": "0.32.2",
+    "eslint-plugin-prettier": "^5.0.1",
+    "eslint-plugin-vue": "^9.19.2",
+    "jsdom": "^23.0.1",
+    "prettier": "^3.1.0",
+    "ts-jest": "^29.1.1",
+    "typescript": "^5.3.3",
+    "vitest": "1.0.2",
     "vue-jest": "^3.0.7"
   }
 }

+ 1 - 2
pages/cmf_licence/organization.vue

@@ -37,13 +37,12 @@
 
 <script setup lang="ts">
 import File from "~/models/Core/File"
-import {ComputedRef, Ref} from "vue";
+import type {ComputedRef, Ref} from "vue";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {useSseStore} from "~/stores/sse";
 import {useEntityManager} from "~/composables/data/useEntityManager";
 import LicenceCmfOrganizationER from "~/models/Export/LicenceCmfOrganizationER";
 import {useDownloadFile} from "~/composables/utils/useDownloadFile";
-import {useRepo} from "pinia-orm";
 
 const { em } = useEntityManager()
 const { getRef } = useEntityFetch()

+ 2 - 1
pages/organization/index.vue

@@ -447,7 +447,8 @@ Contient toutes les informations sur l'organization courante
 
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import TypeOfPractice from "~/models/Organization/TypeOfPractice";
-import {computed, ComputedRef, reactive, ref} from "@vue/reactivity";
+import {computed, reactive, ref} from "@vue/reactivity";
+import type {ComputedRef} from "@vue/reactivity";
 import {useExtensionPanel} from "~/composables/layout/useExtensionPanel";
 import {useRoute} from "#app";
 import { useValidation } from "~/composables/form/useValidation";

+ 9 - 2
pages/parameters.vue

@@ -1,13 +1,20 @@
 <!-- Page de détails des paramètres -->
 
 <template>
-  <LayoutContainer>
+  <NuxtLayout name="parameters">
     <!-- Rend le contenu de la page -->
     <NuxtPage />
-  </LayoutContainer>
+  </NuxtLayout>
 </template>
 
 <script setup lang="ts">
+  /**
+   * Disable the default layout, the page will use the layout defined with <NuxtLayout />
+   * @see https://nuxt.com/docs/guide/directory-structure/layouts#overriding-a-layout-on-a-per-page-basis
+   */
+  definePageMeta({
+    layout: false,
+  });
 </script>
 
 <style scoped lang="scss">

+ 9 - 3
components/Layout/Parameters/Attendances.vue → pages/parameters/attendances.vue

@@ -1,9 +1,14 @@
 <template>
   <LayoutContainer>
     <UiLoadingPanel v-if="pending" />
-    <UiForm v-else :model="Parameters" :entity="parameters">
+    <UiForm
+        v-else
+        :model="Parameters"
+        :entity="parameters"
+        action-position="bottom"
+    >
       <v-row>
-        <v-col cols="6">
+        <v-col cols="12">
           <UiInputCheckbox
             v-model="parameters.sendAttendanceEmail"
             field="sendAttendanceEmail"
@@ -28,7 +33,8 @@
 import Parameters from '~/models/Organization/Parameters'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
-import { AsyncData } from '#app'
+import type { AsyncData } from '#app'
+
 
 const { fetch } = useEntityFetch()
 

+ 4 - 4
components/Layout/Parameters/Bulletin.vue → pages/parameters/bulletin.vue

@@ -6,9 +6,10 @@
         v-else
         :model="Parameters"
         :entity="parameters"
+        action-position="bottom"
     >
       <v-row>
-        <v-col cols="6">
+        <v-col cols="12">
           <UiInputCheckbox
               v-model="parameters.bulletinWithTeacher"
               field="bulletinWithTeacher"
@@ -33,9 +34,7 @@
               v-model="parameters.bulletinEditWithoutEvaluation"
               field="bulletinEditWithoutEvaluation"
           />
-        </v-col>
 
-        <v-col cols="6">
           <UiInputCheckbox
               v-model="parameters.bulletinPrintAddress"
               field="bulletinPrintAddress"
@@ -60,6 +59,7 @@
               v-model="parameters.bulletinReceiver"
               field="bulletinReceiver"
               enum-name="organization_bulletin_send_to"
+              variant="underlined"
           />
         </v-col>
       </v-row>
@@ -71,7 +71,7 @@
 import Parameters from "~/models/Organization/Parameters";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import {AsyncData} from "#app";
+import type {AsyncData} from "#app";
 
 const { fetch } = useEntityFetch()
 

+ 15 - 15
pages/parameters/cycles/[id].vue

@@ -1,18 +1,20 @@
 <template>
   <LayoutContainer>
-    <h2>{{ $t('cycle') }}</h2>
-    <UiFormEdition
-        :model="Cycle"
-        :go-back-route="goBackRoute"
-    >
-      <template v-slot="{ entity }">
-        <UiInputText
-            field="label"
-            v-model="entity.label"
-            :rules="rules()"
-        />
-      </template>
-    </UiFormEdition>
+    <div>
+      <h2>{{ $t('cycle') }}</h2>
+      <UiFormEdition
+          :model="Cycle"
+          go-back-route="/parameters/teaching"
+      >
+        <template v-slot="{ entity }">
+          <UiInputText
+              field="label"
+              v-model="entity.label"
+              :rules="rules()"
+          />
+        </template>
+      </UiFormEdition>
+    </div>
   </LayoutContainer>
 </template>
 <script setup lang="ts">
@@ -21,8 +23,6 @@ import Cycle from "~/models/Education/Cycle";
 
 const i18n = useI18n()
 
-const goBackRoute = { path: `/parameters`, query: { tab: 'teaching' } }
-
 const rules = () => [
   (label: string | null) => (label !== null && label.length > 0) || i18n.t('please_enter_a_value'),
 ]

+ 0 - 10
pages/parameters/cycles/index.vue

@@ -1,10 +0,0 @@
-<template>
-</template>
-
-<script setup lang="ts">
-/** Redirect to /parameters?tab=teaching */
-const router = useRouter()
-router.push(
-    { path: `/parameters`, query: { tab: 'teaching' } }
-)
-</script>

+ 7 - 4
components/Layout/Parameters/EducationNotation.vue → pages/parameters/education_notation.vue

@@ -5,14 +5,16 @@
         v-else
         :model="Parameters"
         :entity="parameters"
+        action-position="bottom"
     >
       <v-row>
-        <v-col cols="6">
+        <v-col cols="12">
           <UiInputCheckbox
               v-model="parameters.periodValidation"
               field="periodValidation"
               label="define_validation_periods_for_teachers"
           />
+
           <UiInputCheckbox
               v-model="parameters.editCriteriaNotationByAdminOnly"
               field="editCriteriaNotationByAdminOnly"
@@ -24,10 +26,9 @@
               v-model="parameters.advancedEducationNotationType"
               enum-name="advanced_education_notation"
               field="advancedEducationNotationType"
+              variant="underlined"
           />
-        </v-col>
 
-        <v-col cols="6">
           <UiInputCheckbox
               v-model="parameters.requiredValidation"
               field="requiredValidation"
@@ -38,6 +39,7 @@
               v-model="parameters.educationPeriodicity"
               enum-name="education_periodicity"
               field="educationPeriodicity"
+              variant="underlined"
           />
 
           <UiInputNumber
@@ -48,6 +50,7 @@
             :min="1"
             :max="100"
             class="mt-2"
+            variant="underlined"
           />
         </v-col>
       </v-row>
@@ -59,7 +62,7 @@
 import Parameters from "~/models/Organization/Parameters";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import {AsyncData} from "#app";
+import type {AsyncData} from "#app";
 
 const i18n = useI18n()
 const { fetch } = useEntityFetch()

+ 2 - 4
pages/parameters/education_timings/[id].vue

@@ -4,7 +4,7 @@
       <h2>{{ $t('educationTiming') }}</h2>
       <UiFormEdition
         :model="EducationTiming"
-        :go-back-route="goBackRoute"
+        go-back-route="/parameters/education_timings"
       >
         <template v-slot="{ entity }">
           <UiInputNumber
@@ -19,13 +19,11 @@
 </template>
 <script setup lang="ts">
 import EducationTiming from '~/models/Education/EducationTiming'
-import {RouteLocationPathRaw} from 'vue-router'
+import type {RouteLocationPathRaw} from 'vue-router'
 import { useI18n } from 'vue-i18n'
 
 const i18n = useI18n()
 
-const goBackRoute: RouteLocationPathRaw = { path: `/parameters`, query: { tab: 'educationTimings' } }
-
 const rules = () => [
   (timing: string | null) =>
     (timing !== null && parseInt(timing) > 0) || i18n.t('please_enter_a_value'),

+ 95 - 5
pages/parameters/education_timings/index.vue

@@ -1,10 +1,100 @@
 <template>
+  <LayoutContainer>
+    <UiLoadingPanel v-if="pending" />
+    <div v-else>
+      <v-table>
+        <thead>
+          <tr>
+            <td>{{ $t('educationTimings') }}</td>
+            <td></td>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="educationTimings.length > 0" v-for="timing in educationTimings" :key="timing.id">
+            <td class="cycle-editable-cell">
+              {{ timing.timing }}
+            </td>
+            <td class="d-flex flex-row">
+              <v-btn
+                  :flat="true"
+                  icon="fa fa-pen"
+                  class="cycle-edit-icon mr-3"
+                  @click="goToEditPage(timing.id as number)"
+              />
+              <UiButtonDelete
+                  :model="EducationTiming"
+                  :entity="timing"
+                  :flat="true"
+                  class="cycle-edit-icon"
+              />
+            </td>
+          </tr>
+          <tr v-else class="theme-neutral">
+            <td><i>{{ $t('nothing_to_show')}}</i></td>
+            <td></td>
+          </tr>
+        </tbody>
+      </v-table>
+      <v-btn
+          :flat="true"
+          prepend-icon="fa fa-plus"
+          class="theme-primary mt-4"
+          @click="goToCreatePage"
+      >
+        {{ $t('add') }}
+      </v-btn>
+    </div>
+  </LayoutContainer>
+
 </template>
 
 <script setup lang="ts">
-/** Redirect to /parameters?tab=educationTimings */
-const router = useRouter()
-router.push(
-    { path: `/parameters`, query: { tab: 'educationTimings' } }
-)
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import EducationTiming from '~/models/Education/EducationTiming'
+import { useRepo } from 'pinia-orm'
+import EducationTimingsRepository from '~/stores/repositories/EducationTimingsRepository'
+import type {ComputedRef} from "vue";
+import {useOrganizationProfileStore} from "~/stores/organizationProfile";
+import UrlUtils from "~/services/utils/urlUtils";
+
+const organizationProfile = useOrganizationProfileStore()
+
+if (organizationProfile.parametersId === null) {
+  throw new Error('Missing organization parameters id')
+}
+
+const { fetch, fetchCollection } = useEntityFetch()
+
+const { pending } = fetchCollection(EducationTiming)
+
+const educationTimingRepo = useRepo(EducationTimingsRepository)
+
+/**
+ * On récupère les timings via le store
+ * (sans ça, les mises à jour SSE ne seront pas prises en compte)
+ */
+const educationTimings: ComputedRef<Array<EducationTiming>> = computed(() => {
+  return educationTimingRepo.getEducationTimings()
+})
+
+const goToEditPage = (id: number) => {
+  navigateTo(UrlUtils.join('/parameters/education_timings', id))
+}
+
+const goToCreatePage = () => {
+  navigateTo('/parameters/education_timings/new')
+}
 </script>
+
+<style scoped lang="scss">
+.v-table {
+  width: 100%;
+  max-width: 800px;
+}
+
+// TODO: voir à factoriser ces styles, ptêt en faisant un component de ces boutons?
+:deep(.cycle-edit-icon .v-icon) {
+  color: rgb(var(--v-theme-primary));
+  font-size: 18px;
+}
+</style>

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

@@ -4,7 +4,7 @@
       <h2>{{ $t("new_education_timing")}}</h2>
       <UiFormCreation
         :model="EducationTiming"
-        :go-back-route="goBackRoute"
+        go-back-route="/parameters/education_timings"
       >
         <template v-slot="{ entity }">
           <v-container :fluid="true" class="container">
@@ -33,8 +33,6 @@ import { useI18n } from 'vue-i18n'
 
 const i18n = useI18n()
 
-const goBackRoute = { path: `/parameters`, query: { tab: 'educationTimings' } }
-
 const rules = () => [
   (timing: number | null ) =>
     (timing !== null && timing > 0) || i18n.t('please_enter_a_value'),

+ 8 - 6
components/Layout/Parameters/General.vue → pages/parameters/general_parameters.vue

@@ -5,14 +5,16 @@
         v-else
         :model="Parameters"
         :entity="parameters"
+        action-position="bottom"
     >
       <v-row>
-        <v-col cols="6">
+        <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"
           />
 
@@ -21,6 +23,7 @@
               v-model="parameters.startCourseDate"
               field="startCourseDate"
               label="start_date_of_courses"
+              position="left"
               class="my-2"
           />
 
@@ -34,15 +37,15 @@
               v-model="parameters.timezone"
               enum-name="timezone"
               field="timezone"
+              variant="underlined"
           />
-        </v-col>
 
-        <v-col cols="6">
           <UiInputDatePicker
               v-if="organizationProfile.isSchool"
               v-model="parameters.musicalDate"
               field="musicalDate"
               label="start_date_of_activity_season"
+              position="left"
               class="my-2"
           />
 
@@ -51,6 +54,7 @@
               v-model="parameters.endCourseDate"
               field="endCourseDate"
               label="end_date_of_courses"
+              position="left"
               class="my-2"
           />
 
@@ -77,7 +81,7 @@
 import Parameters from "~/models/Organization/Parameters";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import {AsyncData} from "#app";
+import type {AsyncData} from "#app";
 
 const { fetch } = useEntityFetch()
 
@@ -91,6 +95,4 @@ const { data: parameters, pending } = fetch(Parameters, organizationProfile.para
 </script>
 
 <style scoped lang="scss">
-
-
 </style>

+ 9 - 131
pages/parameters/index.vue

@@ -1,136 +1,14 @@
-<!--
-Page Paramètres
--->
 <template>
-  <LayoutContainer>
-    <v-col cols="12" sm="12" md="12">
-      <v-tabs
-          :model-value="currentTab"
-          bg-color="primary"
-          color="on-primary"
-          :grow="true"
-          density="default"
-          @update:model-value="onTabUpdate"
-      >
-        <v-tab v-for="tab in tabs" :value="tab">
-          {{ $t(tab) }}
-        </v-tab>
-      </v-tabs>
-
-      <v-card-text>
-        <v-window v-model="currentTab">
-          <v-window-item value="general_parameters">
-            <LayoutParametersGeneral />
-          </v-window-item>
-
-          <v-window-item value="website">
-            <LayoutParametersWebsite />
-          </v-window-item>
-
-          <v-window-item v-if="organizationProfile.isSchool" value="teaching">
-            <LayoutParametersTeaching />
-          </v-window-item>
-
-          <v-window-item v-if="organizationProfile.isSchool" value="intranet_access">
-            <LayoutParametersIntranet />
-          </v-window-item>
-
-          <v-window-item v-if="organizationProfile.isSchool" value="educationNotations">
-            <LayoutParametersEducationNotation />
-          </v-window-item>
-
-          <v-window-item v-if="organizationProfile.isSchool" value="bulletin">
-            <LayoutParametersBulletin />
-          </v-window-item>
-
-          <v-window-item v-if="organizationProfile.isSchool" value="educationTimings">
-            <LayoutParametersEducationTimings />
-          </v-window-item>
-
-          <v-window-item v-if="organizationProfile.isSchool" value="attendances">
-            <LayoutParametersAttendances />
-          </v-window-item>
-
-          <v-window-item v-if="organizationProfile.isSchool" value="residenceAreas">
-            <LayoutParametersResidenceAreas />
-          </v-window-item>
-
-          <v-window-item v-if="organizationProfile.hasModule('Sms')" value="sms_option">
-            <LayoutParametersSms />
-          </v-window-item>
-
-          <v-window-item value="super_admin">
-            <LayoutParametersSuperAdmin/>
-          </v-window-item>
-        </v-window>
-      </v-card-text>
-
-    </v-col>
-  </LayoutContainer>
 </template>
 
 <script setup lang="ts">
-
-  import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-
-  const organizationProfile = useOrganizationProfileStore()
-
-  const tabs = [
-    'general_parameters',
-    'website',
-    organizationProfile.isSchool ? 'teaching' : null,
-    organizationProfile.isSchool ? 'intranet_access' : null,
-    organizationProfile.isSchool ? 'educationNotations': null,
-    organizationProfile.isSchool ? 'bulletin' : null,
-    organizationProfile.isSchool ? 'educationTimings' : null,
-    organizationProfile.isSchool ? 'attendances' : null,
-    organizationProfile.isSchool ? 'residenceAreas' : null,
-    organizationProfile.hasModule('Sms') ? 'sms_option' : null,
-    'super_admin',
-
-  ].filter((v) => v !== null)
-
-  const router = useRouter()
-  const route = useRoute()
-  let mounted = false
-
-
-  /**
-   * Update the current route's query with a new value for 'tab' parameter
-   * @param tab
-   */
-  const updateQuery = (tab: string) => {
-    router.replace({ query: { ...route.query, tab } })
-  }
-
-  const currentTab: Ref<string | null> = ref(null)
-
-  onMounted(() => {
-    if (!route.query || !route.query.tab || !tabs.includes(route.query.tab as string)) {
-      const tab = tabs[0] ?? 'general_parameters'
-      currentTab.value = tab
-      updateQuery(tab)
-    } else {
-      currentTab.value = route.query.tab as string
-    }
-
-    mounted = true
-  })
-
-  const onTabUpdate = (tab: string) => {
-    if (!mounted) {
-      return
-    }
-    updateQuery(tab)
-    currentTab.value = tab
-  }
+/**
+ * Redirect to /parameters/general_parameters
+ *
+ * Voir aussi : pages/parameters.vue
+ * */
+const router = useRouter()
+router.push(
+    { path: `/parameters/general_parameters` }
+)
 </script>
-
-<style scoped lang="scss">
-
-:deep(.v-tabs .v-btn__content) {
-  text-transform: capitalize;
-  letter-spacing: 0.04em;
-}
-</style>
-

+ 3 - 4
components/Layout/Parameters/Intranet.vue → pages/parameters/intranet.vue

@@ -5,9 +5,10 @@
         v-else
         :model="Parameters"
         :entity="parameters"
+        action-position="bottom"
     >
       <v-row>
-        <v-col cols="6">
+        <v-col cols="12">
           <UiInputCheckbox
               v-model="parameters.generateAttendanceReport"
               field="generateAttendanceReport"
@@ -25,9 +26,7 @@
               field="allowMembersToChangeGivenNameAndName"
               label="allow_members_to_change_their_names_and_firstnames"
           />
-        </v-col>
 
-        <v-col cols="6">
           <UiInputCheckbox
               v-model="parameters.createCourse"
               field="createCourse"
@@ -55,7 +54,7 @@
   import Parameters from "~/models/Organization/Parameters";
   import {useEntityFetch} from "~/composables/data/useEntityFetch";
   import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-  import {AsyncData} from "#app";
+  import type {AsyncData} from "#app";
 
   const { fetch } = useEntityFetch()
 

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

@@ -4,7 +4,7 @@
       <h2>Éditer la zone de résidence</h2>
       <UiFormEdition
         :model="ResidenceArea"
-        :go-back-route="goBackRoute"
+        go-back-route="/parameters/residence_areas"
       >
         <template v-slot="{ entity }">
           <UiInputText
@@ -24,10 +24,6 @@ import ResidenceArea from '~/models/Billing/ResidenceArea'
 import { useI18n } from 'vue-i18n'
 
 const i18n = useI18n()
-const { fetch } = useEntityFetch()
-const router = useRouter()
-
-const goBackRoute = { path: `/parameters`, query: { tab: 'residenceAreas' } }
 
 const rules = () => [
   (label: string | null) =>

+ 92 - 5
pages/parameters/residence_areas/index.vue

@@ -1,10 +1,97 @@
 <template>
+  <LayoutContainer>
+    <UiLoadingPanel v-if="pending" />
+    <div v-else>
+      <v-table>
+        <thead>
+          <tr>
+            <td>{{ $t('residenceAreas') }}</td>
+            <td></td>
+          </tr>
+        </thead>
+        <tbody>
+        <tr
+            v-if="residenceAreas.length > 0"
+            v-for="residenceArea in residenceAreas"
+            :key="residenceArea.id"
+        >
+          <td class="cycle-editable-cell">
+            {{ residenceArea.label }}
+          </td>
+          <td class="d-flex flex-row">
+            <v-btn
+                :flat="true"
+                icon="fa fa-pen"
+                class="cycle-edit-icon mr-3"
+                @click="goToEditPage(residenceArea.id as number)"
+            />
+            <UiButtonDelete
+                :model="ResidenceArea"
+                :entity="residenceArea"
+                :flat="true"
+                class="cycle-edit-icon"
+            />
+          </td>
+        </tr>
+        <tr v-else class="theme-neutral">
+          <td><i>{{ $t('nothing_to_show')}}</i></td>
+          <td></td>
+        </tr>
+        </tbody>
+      </v-table>
+
+      <v-btn
+          :flat="true"
+          prepend-icon="fa fa-plus"
+          class="theme-primary mt-4"
+          @click="goToCreatePage"
+      >
+        {{ $t('add') }}
+      </v-btn>
+    </div>
+  </LayoutContainer>
 </template>
 
 <script setup lang="ts">
-/** Redirect to /parameters?tab=residenceAreas */
-const router = useRouter()
-router.push(
-    { path: `/parameters`, query: { tab: 'residenceAreas' } }
-)
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
+import ResidenceArea from '~/models/Billing/ResidenceArea'
+import { useRepo } from 'pinia-orm'
+import ResidenceAreasRepository from '~/stores/repositories/ResidenceAreasRepository'
+import { useRouter } from 'vue-router'
+import UrlUtils from "~/services/utils/urlUtils";
+
+const residenceAreasRepo = useRepo(ResidenceAreasRepository)
+
+const { fetchCollection } = useEntityFetch()
+
+const { pending } = fetchCollection(ResidenceArea)
+
+/**
+ * On récupère les Residence Area via le store
+ * (sans ça, les mises à jour SSE ne seront pas prises en compte)
+ */
+ const residenceAreas: ComputedRef<Array<ResidenceArea>> = computed(() => {
+  return residenceAreasRepo.getResidenceAreas()
+})
+
+const goToEditPage = (id: number) => {
+  navigateTo(UrlUtils.join('/parameters/residence_areas', id))
+}
+
+const goToCreatePage = () => {
+  navigateTo(`/parameters/residence_areas/new`)
+}
 </script>
+
+<style scoped lang="scss">
+.v-table {
+  width: 100%;
+  max-width: 800px;
+}
+
+// TODO: voir à factoriser ces styles, ptêt en faisant un component de ces boutons?
+:deep(.cycle-edit-icon .v-icon) {
+  color: rgb(var(--v-theme-primary));
+  font-size: 18px;
+}
+</style>

+ 1 - 3
pages/parameters/residence_areas/new.vue

@@ -4,7 +4,7 @@
       <h2>{{ $t('create_a_new_residence_area') }}</h2>
       <UiFormCreation
         :model="ResidenceArea"
-        :go-back-route="goBackRoute"
+        go-back-route="/parameters/residence_areas"
       >
         <template v-slot="{ entity }">
           <v-container :fluid="true" class="container">
@@ -31,8 +31,6 @@ import { useI18n } from 'vue-i18n'
 
 const i18n = useI18n()
 
-const goBackRoute = { path: `/parameters`, query: { tab: 'residenceAreas' } }
-
 const rules = () => [
   (label: string | null) =>
     (label !== null && label.length > 0) || i18n.t('please_enter_a_value'),

+ 31 - 5
components/Layout/Parameters/Sms.vue → pages/parameters/sms.vue

@@ -1,11 +1,16 @@
 <template>
   <div>
-    <UiForm :model="Parameters" :entity="parameters">
+    <UiForm
+        :model="Parameters"
+        :entity="parameters"
+        action-position="bottom"
+    >
       <v-row>
         <v-col cols="12">
           <UiInputText
-            v-model="parameters.smsSenderName"
-            field="smsSenderName"
+              v-model="parameters.smsSenderName"
+              field="smsSenderName"
+              variant="underlined"
           />
         </v-col>
         <v-col cols="12">
@@ -13,13 +18,15 @@
             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"
-              type="password"
+              class="password"
+              variant="underlined"
           />
         </v-col>
       </v-row>
@@ -30,7 +37,7 @@
 import Parameters from '~/models/Organization/Parameters'
 import { useEntityFetch } from '~/composables/data/useEntityFetch'
 import { useOrganizationProfileStore } from '~/stores/organizationProfile'
-import { AsyncData } from '#app'
+import type { AsyncData } from '#app'
 
 const { fetch } = useEntityFetch()
 
@@ -45,3 +52,22 @@ const { data: parameters, pending } = fetch(
   organizationProfile.parametersId
 ) as AsyncData<Parameters, Parameters | true>
 </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
+*/
+@font-face {
+  font-family: 'password';
+  font-style: normal;
+  font-weight: 400;
+  font-display: block;
+  src: url(https://jsbin-user-assets.s3.amazonaws.com/rafaelcastrocouto/password.ttf);
+}
+:deep(.password input) {
+  font-family: 'password';
+}
+
+</style>

+ 1 - 1
pages/parameters/subdomains/[id].vue

@@ -79,7 +79,7 @@
   }
 
   const quit = () => {
-    router.push('/parameters?tab=website')
+    router.push('/parameters/website')
     activationPending.value = false
     pageStore.loading = false
   }

+ 0 - 10
pages/parameters/subdomains/index.vue

@@ -1,10 +0,0 @@
-<template>
-</template>
-
-<script setup lang="ts">
-/** Redirect to /parameters?tab=website */
-const router = useRouter()
-router.push(
-    { path: `/parameters`, query: { tab: 'website' } }
-)
-</script>

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

@@ -39,7 +39,7 @@
         </v-container>
 
         <template #form.button>
-          <NuxtLink :to="goBackRoute" class="no-decoration">
+          <NuxtLink to="/parameters/website" class="no-decoration">
             <v-btn class="mr-4 theme-neutral">
               {{ $t('back') }}
             </v-btn>
@@ -54,9 +54,9 @@
 import { useEntityManager } from '~/composables/data/useEntityManager'
 import Subdomain from '~/models/Organization/Subdomain'
 import { SUBMIT_TYPE } from '~/types/enum/enums'
-import { AnyJson } from '~/types/data'
+import type { AnyJson } from '~/types/data'
 import SubdomainValidation from '~/services/validation/subdomainValidation'
-import { Ref } from '@vue/reactivity'
+import type { Ref } from '@vue/reactivity'
 import { useSubdomainValidation } from '~/composables/form/validation/useSubdomainValidation'
 import _ from 'lodash'
 
@@ -68,11 +68,9 @@ const { subdomainValidation } = useSubdomainValidation()
 //@ts-ignore
 const subdomain: Ref<Subdomain> = ref(em.newInstance(Subdomain) as Subdomain)
 
-const goBackRoute = { path: `/parameters`, query: { tab: 'website' } }
-
 const submitActions = computed(() => {
   let actions: AnyJson = {}
-  actions[SUBMIT_TYPE.SAVE_AND_BACK] = goBackRoute
+  actions[SUBMIT_TYPE.SAVE_AND_BACK] = '/parameters/website'
   return actions
 })
 

+ 37 - 41
components/Layout/Parameters/SuperAdmin.vue → pages/parameters/super_admin.vue

@@ -1,48 +1,40 @@
 <template>
   <div>
-    <v-container>
-      <v-row>
-        <v-col cols="1">
-        </v-col>
-        <v-col cols="4">
-          <div class="explanation">
-            <div class="px-6 d-flex flex-row align-center">
-              <v-icon class="theme-primary">fa fa-info</v-icon>
-            </div>
-            <div class="px-2">
-              {{ $t('super_admin_explanation_text')}}
-            </div>
-          </div>
-        </v-col>
-        <v-col cols="1" />
-        <v-col cols="6">
-          <v-row>
+      <div class="explanation">
+        <div class="px-6 d-flex flex-row align-center">
+          <v-icon class="theme-primary">fa fa-info</v-icon>
+        </div>
+        <div class="px-2">
+          {{ $t('super_admin_explanation_text')}}
+        </div>
+      </div>
 
-          </v-row>
-          <v-row v-if="pending">
-            <UiLoadingPanel/>
-          </v-row>
-          <v-row v-else>
-            <UiForm
-                ref="form"
-                :model="AdminAccess"
-                :entity="adminAccess"
-                class="w-100"
-            >
-              <div class="d-flex flex-row mx-4 my-6">
-                <span>{{ $t('username') }} :</span> <pre> {{ adminAccess.username }}</pre>
-              </div>
+      <UiLoadingPanel v-if="pending"/>
+      <UiForm
+          v-else
+          ref="form"
+          :model="AdminAccess"
+          :entity="adminAccess"
+          class="w-100"
+          action-position="bottom"
+      >
+        <v-table class="mb-4">
+          <tbody>
+            <tr>
+              <td>{{ $t('username') }} : </td>
+              <td>{{ adminAccess.username }}</td>
+            </tr>
+          </tbody>
+        </v-table>
 
-              <UiInputText
-                  field="email"
-                  v-model="adminAccess.email"
-                  :rules="rules()"
-              />
-            </UiForm>
-          </v-row>
-        </v-col>
-      </v-row>
-    </v-container>
+        <UiInputText
+            field="email"
+            v-model="adminAccess.email"
+            :rules="rules()"
+            class="mx-4"
+            variant="underlined"
+        />
+      </UiForm>
   </div>
 </template>
 
@@ -94,4 +86,8 @@ const rules = () => [
     border-right: solid 1px rgb(var(--v-theme-primary));
   }
 }
+
+.v-table td:first-child {
+  width: 180px;
+}
 </style>

+ 13 - 14
components/Layout/Parameters/Teaching.vue → pages/parameters/teaching.vue

@@ -5,6 +5,7 @@
         v-else
         :model="Parameters"
         :entity="parameters"
+        action-position="bottom"
     >
       <v-table>
         <thead>
@@ -33,15 +34,11 @@
         </tbody>
       </v-table>
 
-      <v-row>
-        <v-col cols="6">
-          <UiInputCheckbox
-              v-model="parameters.showEducationIsACollectivePractice"
-              field="showEducationIsACollectivePractice"
-              label="allow_to_configure_teachings_with_played_instrument_choice"
-          />
-        </v-col>
-      </v-row>
+      <UiInputCheckbox
+          v-model="parameters.showEducationIsACollectivePractice"
+          field="showEducationIsACollectivePractice"
+          label="allow_to_configure_teachings_with_played_instrument_choice"
+      />
     </UiForm>
   </LayoutContainer>
 </template>
@@ -50,9 +47,9 @@
 import Parameters from "~/models/Organization/Parameters";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import Cycle from "~/models/Education/Cycle";
-import {AsyncData} from "#app";
+import type {AsyncData} from "#app";
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
-import {AnyJson} from "~/types/data";
+import type {AnyJson} from "~/types/data";
 import {useEnumFetch} from "~/composables/data/useEnumFetch";
 
 const organizationProfile = useOrganizationProfileStore()
@@ -98,11 +95,15 @@ const orderedCycles: ComputedRef<AnyJson> = computed(() => {
 
 
 const goToCycleEditPage = (id: number) => {
-  navigateTo(`parameters/cycles/${id}`)
+  navigateTo(`/parameters/cycles/${id}`)
 }
 </script>
 
 <style scoped lang="scss">
+.v-table {
+  width: 100%;
+  max-width: 800px;
+}
 
 .cycle-edit-icon {
   color: rgb(var(--v-theme-primary));
@@ -111,6 +112,4 @@ const goToCycleEditPage = (id: number) => {
 :deep(.cycle-edit-icon .v-icon) {
   font-size: 18px;
 }
-
-
 </style>

+ 60 - 37
components/Layout/Parameters/Website.vue → pages/parameters/website.vue

@@ -5,27 +5,51 @@
         v-else
         :model="Parameters"
         :entity="parameters"
+        action-position="bottom"
     >
       <v-row>
-        <v-col cols="6">
-          <div class="mb-6">
-            <div>{{ $t('your_opentalent_website_address_is')}} : </div>
-            <div class="ma-2 text-primary">
-              <strong>{{ organizationProfile.website }}</strong>
-            </div>
+        <v-col cols="12">
+          <div class="my-5">
+            <span>{{ $t('your_opentalent_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="underlined"
+              class="my-4"
+          />
+
+          <div>
+            <UiInputText
+                v-model="parameters.otherWebsite"
+                field="otherWebsite"
+                variant="underlined"
+                class="my-4"
+            />
+          </div>
+
+          <v-divider class="my-10"/>
+
           <div class="mb-6">
-            <div>{{ $t('your_subdomains') }} : </div>
+            <div><h4>{{ $t('your_subdomains') }} : </h4></div>
             <UiLoadingPanel v-if="subdomainsPending" />
             <div v-else>
-              <v-table class="my-2">
+              <v-table class="subdomains-table my-2">
                 <tbody>
                   <tr
                       v-for="subdomain in subdomains.items"
                       :key="subdomain.id"
                       :title="subdomain.subdomain"
-                      class="subdomainItem"
+                      :class="'subdomainItem' + (subdomain.active ? ' active' : '')"
                       @click="goToEditPage(subdomain.id)"
                   >
                     <td>{{ subdomain.subdomain }}</td>
@@ -43,27 +67,21 @@
 
               </v-table>
 
-              <v-btn
-                  :disabled="!canAddNewSubdomain"
-                  class="my-2"
-                  @click="onAddSubdomainClick"
-              >
-                {{ $t('record_a_new_subdomain')}}
-              </v-btn>
+              <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>
-        </v-col>
 
-        <v-col cols="6">
-            <!-- les publicationDirectors sont des entités Access -->
-            <UiInputAutocompleteAccesses
-                v-model="parameters.publicationDirectors"
-                field="publicationDirectors"
-                multiple
-                chips
-            />
+          <v-divider class="my-10"/>
 
-          <div class="my-8" v-if="!organizationProfile.isCmf">
+          <div class="my-8 d-flex flex-row justify-center w-100" v-if="!organizationProfile.isCmf">
             <v-btn
               v-if="!parameters.desactivateOpentalentSiteWeb"
               color="error"
@@ -105,13 +123,6 @@
               </template>
             </LazyLayoutDialog>
           </div>
-
-          <div>
-            <UiInputText
-                v-model="parameters.otherWebsite"
-                field="otherWebsite"
-            />
-          </div>
         </v-col>
       </v-row>
     </UiForm>
@@ -122,7 +133,7 @@
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
 import Parameters from "~/models/Organization/Parameters";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
-import {AsyncData} from "#app";
+import type {AsyncData} from "#app";
 import Subdomain from "~/models/Organization/Subdomain";
 
 const i18n = useI18n()
@@ -142,7 +153,8 @@ const { data: subdomains, pending: subdomainsPending } = fetchCollection(Subdoma
 const canAddNewSubdomain: ComputedRef<boolean> = computed(() => subdomains.value && subdomains.value.items.length < 3)
 
 const goToEditPage = (id: number) => {
-  navigateTo(`parameters/subdomains/${id}`)
+  console.log(parameters.value)
+  navigateTo(`/parameters/subdomains/${id}`)
 }
 
 const onAddSubdomainClick = () => {
@@ -165,11 +177,14 @@ const reactivateWebsite = () => {
 </script>
 
 <style scoped lang="scss">
-.v-table {
-  background: transparent;
+
+.subdomains-table {
+  max-width: 450px;
 }
+
 .subdomainItem {
   cursor: pointer;
+  border: solid 1px rgb(var(--v-theme-neutral-strong));
 }
 .subdomainItem:hover {
   background: rgb(var(--v-theme-neutral));
@@ -178,4 +193,12 @@ const reactivateWebsite = () => {
   font-size: 12px;
 }
 
+.subdomainItem td:first-child {
+  border-left: solid 2px rgb(var(--v-theme-neutral));
+}
+
+.subdomainItem.active td:first-child {
+  border-left: solid 2px rgb(var(--v-theme-primary));
+}
+
 </style>

+ 2 - 1
pages/poc/[id].vue

@@ -31,7 +31,8 @@
 
 <script setup lang="ts">
 import {useEntityManager} from "~/composables/data/useEntityManager";
-import {ref, Ref} from "@vue/reactivity";
+import {ref} from "@vue/reactivity";
+import type {Ref} from "@vue/reactivity";
 import File from "~/models/Core/File";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {useI18n} from "vue-i18n";

+ 2 - 1
pages/poc/index.vue

@@ -28,7 +28,8 @@
 
 <script setup lang="ts">
   import {useEntityManager} from "~/composables/data/useEntityManager";
-  import {computed, ComputedRef, ref, Ref} from "@vue/reactivity";
+  import {computed, ref} from "@vue/reactivity";
+  import type {ComputedRef, Ref} from "@vue/reactivity";
   import ApiResource from "~/models/ApiResource";
   import File from "~/models/Core/File";
 

+ 0 - 1
pages/poc/new.vue

@@ -20,7 +20,6 @@
 
 <script setup lang="ts">
 import {useEntityManager} from "~/composables/data/useEntityManager";
-import {ref, Ref} from "@vue/reactivity";
 import ApiResource from "~/models/ApiResource";
 import File from "~/models/Core/File";
 

Некоторые файлы не были показаны из-за большого количества измененных файлов