Ver código fonte

Merge branch 'V8-7093-fix_bug_autocomplete_on_saven' into feature/V8-7093-corrections-graphiques-page-para

Olivier Massot 9 meses atrás
pai
commit
c6ab8067f5

+ 9 - 17
components/Ui/Form.vue

@@ -37,7 +37,7 @@ de quitter si des données ont été modifiées.
       <div v-else class="mt-12" />
 
       <!-- Content -->
-      <slot v-bind="{ model, entity }" />
+      <slot v-bind="{ modelValue }" />
 
       <!-- Bottom action bar -->
       <v-container
@@ -105,16 +105,9 @@ import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
 
 const props = defineProps({
   /**
-   * Classe de l'ApiModel (ex: Organization, Notification, ...)
+   * Instance de l'ApiModel
    */
-  model: {
-    type: Function as any as () => typeof ApiModel,
-    required: true,
-  },
-  /**
-   * Instance de l'objet
-   */
-  entity: {
+  modelValue: {
     type: Object as () => ApiModel,
     required: true,
   },
@@ -209,7 +202,7 @@ const closeConfirmationDialog = () => {
   formStore.setShowConfirmToLeave(false)
 }
 
-const emit = defineEmits(['update:entity'])
+const emit = defineEmits(['update:model-value'])
 
 
 // ### Actions du formulaire
@@ -234,10 +227,9 @@ const submit = async (next: string | null = null) => {
   try {
     usePageStore().loading = true
 
-    // TODO: est-ce qu'il faut re-fetch l'entité après le persist?
-    const updatedEntity = await em.persist(props.entity)
+    const updatedEntity = await em.persist(props.modelValue)
 
-    emit('update:entity', updatedEntity)
+    emit('update:model-value', updatedEntity)
 
     if (props.refreshProfile) {
       await refreshProfile()
@@ -348,7 +340,7 @@ const cancel = () => {
 
   formStore.setShowConfirmToLeave(false)
 
-  em.reset(props.model, props.entity)
+  em.reset(props.modelValue)
 
   if (requestedLeavingRoute.value !== null) {
     navigateTo(requestedLeavingRoute.value)
@@ -367,7 +359,7 @@ const actions = computed(() => {
  */
 const onFormChange = async () => {
   if (isValid.value) {
-    em.save(props.entity)
+    em.save(props.modelValue)
     setIsDirty(true)
 
     if (props.onChanged) {
@@ -390,7 +382,7 @@ const validate = async function () {
 }
 
 // #### Gestion de l'état dirty
-watch(props.entity, async (newEntity, oldEntity) => {
+watch(props.modelValue, async (newEntity, oldEntity) => {
   setIsDirty(true)
 })
 

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

@@ -1,5 +1,5 @@
 <template>
-  <UiForm :model="model" :entity="entity" :submitActions="submitActions">
+  <UiForm v-model="entity" :submitActions="submitActions">
     <template #form.button>
       <v-btn v-if="goBackRoute" class="theme-neutral mr-3" @click="quit">
         {{ $t('cancel') }}

+ 1 - 2
components/Ui/Form/Edition.vue

@@ -3,8 +3,7 @@
     <UiLoadingPanel v-if="pending" />
     <UiForm
       v-else
-      :model="model"
-      :entity="entity"
+      v-model="entity"
       :submitActions="submitActions"
     >
       <template #form.button>

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

@@ -18,6 +18,7 @@ Champs de saisie de type Text dédié à la saisie d'emails
 import { useNuxtApp } from '#app'
 import { useFieldViolation } from '~/composables/form/useFieldViolation'
 import { useValidationUtils } from '~/composables/utils/useValidationUtils'
+import type {PropType} from '@vue/runtime-core';
 
 const props = defineProps({
   label: {
@@ -54,6 +55,23 @@ const props = defineProps({
     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: 'outlined',
+  },
 })
 
 const { emit, i18n } = useNuxtApp()

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

@@ -125,7 +125,7 @@ const props = defineProps({
       | undefined
     >,
     required: false,
-    default: 'filled',
+    default: 'outlined',
   },
 })
 

+ 12 - 0
composables/utils/useRouteUtils.ts

@@ -0,0 +1,12 @@
+export const useRouteUtils = () => {
+  const route = useRoute()
+
+  const getIdFromRoute = (): number => {
+    if (!route.params.id || !/\d+/.test(route.params.id as string)) {
+      throw new Error('No id found in route')
+    }
+    return parseInt(route.params.id as string)
+  }
+
+  return { getIdFromRoute }
+}

+ 5 - 2
i18n/lang/fr.json

@@ -186,7 +186,7 @@
   "educationTiming": "Durée d'un enseignement (en minutes)",
   "new_education_timings": "Nouvelle durée d'enseignement",
   "superAdmin": "Compte super-admin",
-  "username": "Login de connexion",
+  "username": "Nom d'utilisateur",
   "residenceArea": "Zones de résidence",
   "deactivateOpentalentSiteWeb": "Désactiver le site opentalent",
   "reactivateOpentalentSiteWeb": "Réactiver le site Opentalent",
@@ -705,5 +705,8 @@
   "teachers": "Professeurs",
   "pupils-members": "Élèves / Adhérents / Membres",
   "id": "Id",
-  "missing_name": "Nom manquant"
+  "missing_name": "Nom manquant",
+  "warning": "Avertissement",
+  "please_enter_a_value_for_the_sms_sender_name": "Le nom d'expediteur ne doit pas comporter plus de 11 caractères, et être composé uniquement de chiffres et/ou de lettres.",
+  "associated_email": "Adresse Email associée"
 }

+ 1 - 2
pages/my-settings.vue

@@ -11,8 +11,7 @@ Page 'Mes préférences'
               <UiLoadingPanel v-if="pending" />
               <UiForm
                 v-else
-                :model="Preferences"
-                :entity="preferences"
+                v-model="preferences"
                 action-position="bottom"
               >
                 <v-row>

+ 1 - 3
pages/parameters/attendances.vue

@@ -4,9 +4,7 @@
       <UiLoadingPanel v-if="pending" />
       <UiForm
         v-else-if="parameters !== null"
-        :model="Parameters"
-        :entity="parameters"
-        action-position="bottom"
+        v-model="parameters"
       >
         <v-row>
           <v-col cols="12">

+ 1 - 3
pages/parameters/bulletin.vue

@@ -3,9 +3,7 @@
     <UiLoadingPanel v-if="pending" />
     <UiForm
       v-else
-      :model="Parameters"
-      :entity="parameters"
-      action-position="bottom"
+      v-model="parameters"
     >
       <v-row>
         <v-col cols="12">

+ 1 - 3
pages/parameters/education_notation.vue

@@ -3,9 +3,7 @@
     <UiLoadingPanel v-if="pending" />
     <UiForm
       v-else
-      :model="Parameters"
-      :entity="parameters"
-      action-position="bottom"
+      v-model="parameters"
     >
       <v-row>
         <v-col cols="12">

+ 1 - 3
pages/parameters/general_parameters.vue

@@ -3,9 +3,7 @@
     <UiLoadingPanel v-if="pending" />
     <UiForm
       v-else-if="parameters !== null"
-      :model="Parameters"
-      :entity="parameters"
-      action-position="bottom"
+      v-model="parameters"
     >
       <v-row>
         <v-col cols="12">

+ 1 - 3
pages/parameters/intranet.vue

@@ -3,9 +3,7 @@
     <UiLoadingPanel v-if="pending" />
     <UiForm
       v-else
-      :model="Parameters"
-      :entity="parameters"
-      action-position="bottom"
+      v-model="parameters"
     >
       <v-row>
         <v-col cols="12">

+ 13 - 3
pages/parameters/sms.vue

@@ -2,15 +2,14 @@
   <div>
     <UiForm
       v-if="parameters"
-      :model="Parameters"
-      :entity="parameters"
-      action-position="bottom"
+      v-model="parameters"
     >
       <v-row>
         <v-col cols="12">
           <UiInputText
             v-model="parameters.smsSenderName"
             field="smsSenderName"
+            :rules="rules()"
             variant="underlined"
           />
         </v-col>
@@ -44,6 +43,8 @@ definePageMeta({
   name: 'parameters_sms_page',
 })
 
+const i18n = useI18n()
+
 const { fetch } = useEntityFetch()
 
 const organizationProfile = useOrganizationProfileStore()
@@ -56,6 +57,15 @@ const { data: parameters } = fetch(
   Parameters,
   organizationProfile.parametersId,
 ) as AsyncData<Parameters | null, Error | null>
+
+/**
+ * Règles de validation
+ */
+const rules = () => [
+  (smsSenderName: string | null) =>
+    (smsSenderName !== null && /^\w{3,11}$/.test(smsSenderName)) ||
+    i18n.t('please_enter_a_value_for_the_sms_sender_name'),
+]
 </script>
 
 <style scoped lang="scss">

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

@@ -44,19 +44,18 @@ import { useEntityManager } from '~/composables/data/useEntityManager'
 import { usePageStore } from '~/stores/page'
 import { TYPE_ALERT } from '~/types/enum/enums'
 import { useRefreshProfile } from '~/composables/data/useRefreshProfile'
+import { useRouteUtils } from '~/composables/utils/useRouteUtils'
 
 const { em } = useEntityManager()
 const { fetch } = useEntityFetch()
 
 const router = useRouter()
-const route = useRoute()
 
 const { refreshProfile } = useRefreshProfile()
 
-if (!route.params.id || /\d+/.test(route.params.id as string)) {
-  throw new Error('no id found')
-}
-const id: number = parseInt(route.params.id as string)
+const { getIdFromRoute } = useRouteUtils()
+
+const id = getIdFromRoute()
 
 const { data: subdomain, pending } = fetch(Subdomain, id)
 

+ 9 - 3
pages/parameters/subdomains/new.vue

@@ -3,8 +3,7 @@
     <LayoutContainer>
       <UiForm
         ref="form"
-        :model="Subdomain"
-        :entity="subdomain"
+        v-model="subdomain"
         :submit-actions="submitActions"
         :validation-pending="validationPending"
         :refresh-profile="true"
@@ -66,8 +65,15 @@ const i18n = useI18n()
 const { em } = useEntityManager()
 const { subdomainValidation } = useSubdomainValidation()
 
+const organizationProfileStore = useOrganizationProfileStore()
+
 // @ts-expect-error TODO à résoudre quand l'EM pourra gérer les types génériques
-const subdomain: Ref<Subdomain> = ref(em.newInstance(Subdomain))
+const subdomain: Ref<Subdomain> = ref(
+  em.newInstance(
+    Subdomain,
+    { organization: organizationProfileStore.id }
+  )
+)
 
 const submitActions = computed(() => {
   const actions: AnyJson = {}

+ 13 - 17
pages/parameters/super_admin.vue

@@ -1,8 +1,8 @@
 <template>
   <div>
     <div class="explanation">
-      <div class="px-6 d-flex flex-row align-center">
-        <v-icon class="theme-primary">fa fa-info</v-icon>
+      <div class="px-4 d-flex flex-row align-center">
+        <v-icon class="theme-info">fa fa-info</v-icon>
       </div>
       <div class="px-2">
         {{ $t('super_admin_explanation_text') }}
@@ -13,26 +13,23 @@
     <UiForm
       v-else-if="adminAccess"
       ref="form"
-      :model="AdminAccess"
-      :entity="adminAccess"
+      v-model="adminAccess"
       class="w-100"
-      action-position="bottom"
     >
       <v-table class="mb-4">
         <tbody>
           <tr>
             <td>{{ $t('username') }} :</td>
-            <td>{{ adminAccess.username }}</td>
+            <td><b>{{ adminAccess.username }}</b></td>
           </tr>
         </tbody>
       </v-table>
 
-      <UiInputText
+      <UiInputEmail
         v-model="adminAccess.email"
         field="email"
-        :rules="rules()"
+        :label="$t('associated_email')"
         class="mx-4"
-        variant="underlined"
       />
     </UiForm>
     <span v-else>{{ $t('no_admin_access_recorded') }}</span>
@@ -72,23 +69,22 @@ const rules = () => [
 .explanation {
   display: flex;
   flex-direction: row;
-  padding: 60px 26px;
+  margin: 32px;
+  padding: 8px 4px;
+  border-radius: 6px;
   text-align: justify;
-  color: rgb(var(--v-theme-neutral-strong));
+  color: rgb(var(--v-theme-info));
+  border: solid 1px rgb(var(--v-theme-info));
 
   .v-icon {
-    background-color: rgb(var(--v-theme-primary));
+    color: rgb(var(--v-theme-info));
     font-size: 22px;
     border-radius: 16px;
-    margin: 3px;
+    margin: 3px 1px;
     padding: 3px;
     height: 28px;
     width: 28px;
   }
-
-  div:first-child {
-    border-right: solid 1px rgb(var(--v-theme-primary));
-  }
 }
 
 .v-table td:first-child {

+ 1 - 3
pages/parameters/teaching.vue

@@ -3,9 +3,7 @@
     <UiLoadingPanel v-if="pending" />
     <UiForm
       v-else-if="parameters !== null"
-      :model="Parameters"
-      :entity="parameters"
-      action-position="bottom"
+      v-model="parameters"
     >
       <LayoutParametersTable
         :items="tableItems"

+ 7 - 8
pages/parameters/website.vue

@@ -4,8 +4,7 @@
     <UiForm
       v-else-if="parameters !== null"
       :model="Parameters"
-      :entity="parameters"
-      @update:entity="refresh"
+      v-model="parameters"
     >
       <v-row>
         <v-col cols="12">
@@ -158,7 +157,7 @@ if (organizationProfile.parametersId === null) {
   throw new Error('Missing organization parameters id')
 }
 
-const { data: parameters, pending, refresh } = fetch(
+const { data: parameters, pending } = fetch(
   Parameters,
   organizationProfile.parametersId,
 ) as AsyncData<ApiResource | null, Error | null>
@@ -176,7 +175,6 @@ const canAddNewSubdomain: ComputedRef<boolean> = computed(
 )
 
 const goToEditPage = (id: number) => {
-  console.log(parameters.value)
   navigateTo(`/parameters/subdomains/${id}`)
 }
 
@@ -226,12 +224,13 @@ const onDialogYesBtnClick = () => {
   border-left: solid 2px rgb(var(--v-theme-neutral));
 }
 
-.subdomainItem.active td:first-child {
-  border-left: solid 2px rgb(var(--v-theme-primary));
-}
-.subdomainItem.active td:last-child {
+.subdomainItem td:last-child {
   border-top: solid 1px rgb(var(--v-theme-neutral));
   border-bottom: solid 1px rgb(var(--v-theme-neutral));
   border-right: solid 1px rgb(var(--v-theme-neutral));
 }
+
+.subdomainItem.active td:first-child {
+  border-left: solid 2px rgb(var(--v-theme-primary));
+}
 </style>

+ 7 - 2
plugins/init.server.ts

@@ -8,7 +8,7 @@ export default defineNuxtPlugin(async () => {
 
   const bearer: CookieRef<string | null> = useCookie('BEARER') ?? null
   const accessCookieId: CookieRef<string | null> = useCookie('AccessId') ?? null
-  const switchId: CookieRef<string | null> = useCookie('SwitchAccessId') ?? null
+  const switchCookieId: CookieRef<string | null> = useCookie('SwitchAccessId') ?? null
 
   if (accessCookieId.value === null || Number.isNaN(accessCookieId.value)) {
     redirectToLogout()
@@ -21,13 +21,18 @@ export default defineNuxtPlugin(async () => {
     return
   }
 
+  let switchId: number | null = parseInt(switchCookieId.value ?? '')
+  if (isNaN(switchId)) {
+    switchId = null
+  }
+
   const { initiateProfile } = useRefreshProfile()
 
   try {
     await initiateProfile(
       accessId,
       bearer.value ?? '',
-      switchId.value !== null ? parseInt(switchId.value) : null,
+      switchId,
     )
   } catch (error) {
     if (error instanceof UnauthorizedError) {

+ 10 - 4
services/data/entityManager.ts

@@ -65,6 +65,10 @@ class EntityManager {
     return this.getRepository(model).where((val) => Number.isInteger(val.id))
   }
 
+  public getModel(instance: ApiResource): typeof ApiResource {
+    return instance.constructor as typeof ApiModel
+  }
+
   /**
    * Cast an object as an ApiResource
    * This in used internally to ensure the object is recognized as an ApiResource
@@ -140,7 +144,7 @@ class EntityManager {
    *                  record is also updated.
    */
   public save(instance: ApiResource, permanent: boolean = false): ApiResource {
-    const model = instance.constructor as typeof ApiResource
+    const model = this.getModel(instance)
 
     this.validateEntity(instance)
 
@@ -269,7 +273,7 @@ class EntityManager {
    * @param instance
    */
   public async persist(instance: ApiModel) {
-    const model = instance.constructor as typeof ApiModel
+    const model = this.getModel(instance)
 
     let url = UrlUtils.join('api', model.entity)
     let response
@@ -330,7 +334,7 @@ class EntityManager {
    * @param instance
    */
   public async delete(instance: ApiModel) {
-    const model = instance.constructor as typeof ApiModel
+    const model = this.getModel(instance)
     instance = this.cast(model, instance)
 
     console.log('delete', instance)
@@ -357,7 +361,9 @@ class EntityManager {
    * @param model
    * @param instance
    */
-  public reset(model: typeof ApiResource, instance: ApiResource) {
+  public reset(instance: ApiResource) {
+    const model = this.getModel(instance)
+
     const initialInstance = this.getInitialStateOf(model, instance.id)
     if (initialInstance === null) {
       throw new Error(