瀏覽代碼

contact points: implements validation, create email and phone input

Olivier Massot 4 年之前
父節點
當前提交
1f5a51e233

+ 5 - 5
components/Ui/Form.vue

@@ -10,13 +10,13 @@ Formulaire générique
       ref="form"
       v-model="properties.valid"
       lazy-validation
-      :readonly="readOnly"
+      :readonly="readonly"
     >
       <v-container fluid class="container btnActions">
         <v-row>
           <v-col cols="12" sm="12">
             <slot name="form.button" />
-            <v-btn v-if="!readOnly" class="mr-4 ot_green ot_white--text" @click="submit">
+            <v-btn v-if="!readonly" class="mr-4 ot_green ot_white--text" @click="submit">
               {{ $t('save') }}
             </v-btn>
           </v-col>
@@ -29,7 +29,7 @@ Formulaire générique
         <v-row>
           <v-col cols="12" sm="12">
             <slot name="form.button" />
-            <v-btn v-if="!readOnly" class="mr-4 ot_green ot_white--text" @click="submit">
+            <v-btn v-if="!readonly" class="mr-4 ot_green ot_white--text" @click="submit">
               {{ $t('save') }}
             </v-btn>
           </v-col>
@@ -101,7 +101,7 @@ export default defineComponent({
       saveKo: false
     })
 
-    const readOnly: Ref<boolean> = ref(false)
+    const readonly: Ref<boolean> = ref(false)
 
     const entry: ComputedRef<AnyJson> = computed(() => {
       return queryHelper.getFlattenEntry(query.value, id.value)
@@ -166,7 +166,7 @@ export default defineComponent({
       submit,
       updateRepository,
       properties,
-      readOnly,
+      readonly,
       showDialog,
       entry,
       goEvenUnsavedData,

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

@@ -45,7 +45,7 @@ export default defineComponent({
       required: false,
       default: () => []
     },
-    readOnly: {
+    readonly: {
       type: Boolean,
       required: false
     },

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

@@ -47,7 +47,7 @@ export default defineComponent({
       required: false,
       default: null
     },
-    readOnly: {
+    readonly: {
       type: Boolean,
       required: false
     },

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

@@ -12,7 +12,7 @@ Case à cocher
     <v-checkbox
       :value="data"
       :label="$t(label_field)"
-      :disabled="readOnly"
+      :disabled="readonly"
       @change="$emit('update', $event, field)"
     />
   </v-container>
@@ -37,7 +37,7 @@ export default defineComponent({
       type: Boolean,
       required: false
     },
-    readOnly: {
+    readonly: {
       type: Boolean,
       required: false
     }

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

@@ -20,7 +20,7 @@ Sélecteur de dates
           autocomplete="off"
           :label="$t(label_field)"
           prepend-icon="mdi-calendar"
-          :disabled="readOnly"
+          :disabled="readonly"
           v-bind="attrs"
           :dense="dense"
           :single-line="singleLine"
@@ -60,7 +60,7 @@ export default defineComponent({
       required: false,
       default: null
     },
-    readOnly: {
+    readonly: {
       type: Boolean,
       required: false
     },

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

@@ -0,0 +1,82 @@
+<!--
+Champs de saisie de type Text dédié à la saisie d'emails
+-->
+
+<template>
+  <UiInputText
+    :data="data"
+    :label="label"
+    :readonly="readonly"
+    :error="error"
+    :error-message="errorMessage"
+    :rules="rules"
+    @change="$emit('update', $event, field)"
+  />
+</template>
+
+<script lang="ts">
+import { defineComponent, useContext } from '@nuxtjs/composition-api'
+
+export default defineComponent({
+  props: {
+    label: {
+      type: String,
+      required: false,
+      default: ''
+    },
+    field: {
+      type: String,
+      required: false,
+      default: null
+    },
+    data: {
+      type: [String, Number],
+      required: false,
+      default: null
+    },
+    readonly: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    required: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    error: {
+      type: Boolean,
+      required: false
+    },
+    errorMessage: {
+      type: String,
+      required: false,
+      default: null
+    }
+  },
+  setup (props) {
+    const { app: { i18n } } = useContext()
+
+    const rules = [
+      (email: string) => validEmail(email) || i18n.t('email_error')
+    ]
+
+    if (props.required) {
+      rules.push(
+        (email: string) => !!email || i18n.t('required')
+      )
+    }
+
+    return {
+      rules
+    }
+  }
+})
+
+function validEmail(email: string) {
+  // regex from https://fr.vuejs.org/v2/cookbook/form-validation.html#Utiliser-une-validation-personnalisee
+  const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+  return re.test(email)
+}
+
+</script>

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

@@ -20,7 +20,7 @@ Liste déroulante dédiée à l'affichage d'objets Enum
       item-text="label"
       item-value="value"
       :rules="rules"
-      :disabled="readOnly"
+      :disabled="readonly"
       @change="$emit('update', $event, field)"
     />
   </main>
@@ -52,7 +52,7 @@ export default defineComponent({
       required: false,
       default: null
     },
-    readOnly: {
+    readonly: {
       type: Boolean,
       required: false
     },

+ 45 - 23
components/Ui/Input/Phone.vue

@@ -1,20 +1,29 @@
 <!--
-CHamps de saisie d'un numéro de téléphone
+Champs de saisie d'un numéro de téléphone
 
 @see https://github.com/yogakurniawan/vue-tel-input-vuetify
 -->
 
 <template>
-  <vue-tel-input-vuetify
-    :field="field"
-    :label="label"
-    :value="data"
-    @input="onChange"
-  />
+  <client-only>
+    <vue-tel-input-vuetify
+      :field="field"
+      :label="label"
+      :value="data"
+      :readonly="readonly"
+      clearable
+      valid-characters-only
+      validate-on-blur
+      :rules="rules"
+      :active-country="{iso2: 'FR'}"
+      @input="onInput"
+      @change="onChange"
+    />
+  </client-only>
 </template>
 
 <script lang="ts">
-import { defineComponent } from '@nuxtjs/composition-api'
+import { defineComponent, Ref, ref, useContext } from '@nuxtjs/composition-api'
 
 export default defineComponent({
   props: {
@@ -28,25 +37,15 @@ export default defineComponent({
       required: false,
       default: null
     },
-    type: {
-      type: String,
-      required: false,
-      default: null
-    },
     data: {
       type: [String, Number],
       required: false,
       default: null
     },
-    readOnly: {
+    readonly: {
       type: Boolean,
       required: false
     },
-    rules: {
-      type: Array,
-      required: false,
-      default: () => []
-    },
     error: {
       type: Boolean,
       required: false
@@ -57,17 +56,40 @@ export default defineComponent({
       default: null
     }
   },
-  setup (props) {
+  setup () {
+    const { app: { i18n } } = useContext()
+
+    const nationalNumber: Ref<string | number> = ref('')
+    const internationalNumber: Ref<string | number> = ref('')
+    const isValid: Ref<boolean> = ref(false)
+    const country: Ref<string> = ref('')
+
     return {
-      label_field: props.label ?? props.field
+      nationalNumber,
+      internationalNumber,
+      isValid,
+      country,
+      rules: [
+        () => isValid.value || i18n.t('phone_error')
+      ]
     }
   },
   methods: {
-    onChange (_, { number }) {
-      this.$emit('update', number.e164, this.field)
+    onInput (_: any, { number, valid, country }: { number: any, valid: boolean, country: any }) {
+      this.isValid = valid
+      this.nationalNumber = number.national
+      this.internationalNumber = number.international
+      this.country = country && country.name
+      // console.log(this.field, this.isValid, this.nationalNumber, this.internationalNumber, this.country)
+    },
+    onChange () {
+      if (this.isValid) {
+        this.$emit('update', this.internationalNumber, this.field)
+      }
     }
   }
 })
+
 </script>
 
 <style>

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

@@ -10,7 +10,7 @@ Champs de saisie de texte
     :value="data"
     :label="$t(label_field)"
     :rules="rules"
-    :disabled="readOnly"
+    :disabled="readonly"
     :type="type"
     :error="error"
     :error-messages="errorMessage"
@@ -43,7 +43,7 @@ export default defineComponent({
       required: false,
       default: null
     },
-    readOnly: {
+    readonly: {
       type: Boolean,
       required: false
     },

+ 3 - 1
lang/rulesAndErrors/fr-FR.js

@@ -2,6 +2,8 @@ export default (context, locale) => {
   return ({
     required: 'Ce champs est obligatoire',
     name_length_rule: 'La taille du nom doit être de moins de 128 caractères',
-    siret_error: 'N° de siret non valide'
+    siret_error: 'N° de siret non valide',
+    email_error: 'Adresse email invalide',
+    phone_error: 'Numéro de téléphone invalide'
   })
 }

+ 1 - 1
pages/organization/contact_points/_id.vue

@@ -35,7 +35,7 @@
                   />
                 </v-col>
                 <v-col cols="12" sm="6">
-                  <UiInputText
+                  <UiInputEmail
                     field="email"
                     label="email"
                     :data="entry['email']"

+ 12 - 8
pages/organization/index.vue

@@ -23,10 +23,13 @@ Contient toutes les informations sur l'organization courante
                 </v-col>
 
                 <v-col cols="12" sm="6">
-                  <UiInputText field="siretNumber" :data="entry['siretNumber']"  v-on:update="checkSiretHook($event, 'siretNumber', updateRepository)"
-                                  :error="siretError"
-                                  :errorMessage="siretErrorMessage"
-                                  :rules="rules.siretRule"/>
+                  <UiInputText field="siretNumber"
+                               :data="entry['siretNumber']"
+                               v-on:update="checkSiretHook($event, 'siretNumber', updateRepository)"
+                               :error="siretError"
+                               :errorMessage="siretErrorMessage"
+                               :rules="rules.siretRule"
+                  />
                 </v-col>
 
                 <v-col cols="12" sm="6">
@@ -322,26 +325,27 @@ import {UseNavigationHelpers} from "~/use/form/useNavigationHelpers";
 export default defineComponent({
   name: 'OrganizationParent',
   setup() {
-    const {store, app: {i18n}, $dataProvider} = useContext()
+    const { store, app: {i18n}, $dataProvider } = useContext()
     const organizationProfile = $organizationProfile(store)
     const id: number = store.state.profile.organization.id;
 
     const repositories = getRepositories()
 
-    const {siretError, siretErrorMessage, checkSiret} = UseValidator.useHandleSiret(i18n, $dataProvider)
+    const { siretError, siretErrorMessage, checkSiret } = UseValidator.useHandleSiret(i18n, $dataProvider)
+
     const checkSiretHook = async (siret: string, field: string, updateRepository: any) => {
       await checkSiret(siret)
       if(!siretError.value)
         updateRepository(siret, field);
     }
 
-    const {panel} = UseNavigationHelpers.expansionPanels()
+    const { panel } = UseNavigationHelpers.expansionPanels()
 
     return {
       repositories,
       id,
       organizationProfile,
-      models: {Organization, ContactPoint, BankAccount, OrganizationAddressPostal},
+      models: { Organization, ContactPoint, BankAccount, OrganizationAddressPostal },
       datatableHeaders: getDataTablesHeaders(i18n),
       rules: getRules(i18n),
       siretError,

+ 2 - 2
plugins/phone-input.ts

@@ -10,8 +10,8 @@ export default ({ app } : { app: NuxtAppOptions }) => {
   Vue.use(VueTelInputVuetify, {
     vuetify,
     preferredCountries: ['FR', 'DE', 'CH', 'BE', 'GB'],
+    defaultCountry: 'FR',
     mode: 'international',
-    invalidMsg: app.i18n.t('phoneNumberInvalid'),
-    'valid-characters-only': 'true'
+    invalidMsg: app.i18n.t('phoneNumberInvalid')
   })
 }

+ 1 - 1
services/serializer/denormalizer/yaml.ts

@@ -22,7 +22,7 @@ class Yaml extends BaseDenormalizer {
     try {
       return yaml.load(yaml.dump(read(data.path)))
     } catch (e) {
-      throw new Error('Cannot determine user abilities: ' + e.message)
+      throw new Error(e.message)
     }
   }
 }