Forráskód Böngészése

form saving ok, add subdomain menu layout, minor fixes

Olivier Massot 2 éve
szülő
commit
288aea094d

+ 4 - 1
components/Layout/Parameters/PreferencesTab.vue

@@ -15,6 +15,7 @@
           </UiExpansionPanel>
 
           <UiExpansionPanel title="website" icon="fas fa-info">
+            <LayoutParametersPreferencesTabWebsite :parameters="parameters" />
           </UiExpansionPanel>
 
           <UiExpansionPanel title="teaching" icon="fas fa-info">
@@ -65,6 +66,8 @@ import {Ref} from "@vue/reactivity";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {useOrganizationProfileStore} from "~/stores/organizationProfile";
 import Parameters from "~/models/Organization/Parameters";
+import {AsyncData} from "#app";
+import ApiResource from "~/models/ApiResource";
 
 const openedPanels: Ref<Array<string>> = ref([])
 onMounted(() => {
@@ -91,7 +94,7 @@ if (organizationProfile.parametersId === null) {
   throw new Error('Missing organization parameters id')
 }
 
-const { data: parameters, pending } = fetch(Parameters, organizationProfile.parametersId)
+const { data: parameters, pending } = fetch(Parameters, organizationProfile.parametersId) as AsyncData<Parameters, Parameters | true>
 
 
 

+ 1 - 6
components/Layout/Parameters/PreferencesTab/GeneralParameters.vue

@@ -55,7 +55,7 @@
     <v-row>
       <UiInputCombobox
           v-model="parameters.timezone"
-          :label="$t('timezone')"
+          field="timezone"
           :items="['Europe/Paris', 'Europe/Zurich', 'Indian/Reunion']"
       />
     </v-row>
@@ -73,11 +73,6 @@ const props = defineProps({
     required: true
   }
 })
-
-//
-// studentsAreAdherents
-// timezone
-
 </script>
 
 <style scoped lang="scss">

+ 51 - 0
components/Layout/Parameters/PreferencesTab/Website.vue

@@ -0,0 +1,51 @@
+<template>
+  <v-container>
+    <v-row>
+      <v-col cols="6">
+        <div>{{ $t('your_opentalent_website_is')}} : </div>
+        <div class="mb-1">{{ organizationProfile.website }}</div>
+
+        <div>{{ $t('record_a_new_subdomain')}}</div>
+        <v-text-field class="mb-1"></v-text-field>
+
+        <UiInputText
+          v-model="parameters.otherWebsite"
+          field="otherWebsite"
+        />
+      </v-col>
+
+
+      <v-col cols="6">
+        <div>{{ $t('subdomains_history') }}</div>
+        <div>...</div>
+
+        <UiInputCheckbox
+          v-model="parameters.desactivateOpentalentSiteWeb"
+          field="desactivateOpentalentSiteWeb"
+        />
+
+        <UiInputAutocomplete field="publicationDirectors"/>
+      </v-col>
+    </v-row>
+  </v-container>
+</template>
+
+<script setup lang="ts">
+import {PropType} from "@vue/runtime-core";
+import {useOrganizationProfileStore} from "~/stores/organizationProfile";
+import Parameters from "~/models/Organization/Parameters";
+
+const props = defineProps({
+  parameters: {
+    type: Object as PropType<Parameters>,
+    required: true
+  }
+})
+
+const organizationProfile = useOrganizationProfileStore()
+
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 163 - 126
components/Ui/Form.vue

@@ -1,6 +1,9 @@
 <!--
 Formulaire générique
 
+Assure la validation des données, les actions de base (enregistrement, annulation, ...), et la confirmation avant
+de quitter si des données ont été modifiées.
+
 @see https://vuetifyjs.com/en/components/forms/#usage
 -->
 
@@ -48,7 +51,7 @@ Formulaire générique
 
     <!-- Confirmation dialog -->
     <LazyLayoutDialog
-      :show="showDialog"
+      :show="isConfirmationDialogShowing"
     >
       <template #dialogText>
         <v-card-title class="text-h5 theme-neutral">
@@ -60,7 +63,7 @@ Formulaire générique
         </v-card-text>
       </template>
       <template #dialogBtn>
-        <v-btn class="mr-4 submitBtn theme-primary" @click="closeDialog">
+        <v-btn class="mr-4 submitBtn theme-primary" @click="closeConfirmationDialog">
           {{ $t('back_to_form') }}
         </v-btn>
         <v-btn class="mr-4 submitBtn theme-primary" @click="saveAndQuit">
@@ -88,18 +91,30 @@ import {AnyJson} from "~/types/data";
 import * as _ from 'lodash-es'
 
 const props = defineProps({
+  /**
+   * Classe de l'ApiModel (ex: Organization, Notification, ...)
+   */
   model: {
     type: Function as any as () => typeof ApiModel,
     required: true
   },
+  /**
+   * Instance de l'objet
+   */
   entity: {
     type: Object as () => ApiModel,
     required: true
   },
+  /**
+   * TODO: compléter
+   */
   onChanged: {
     type: Function,
     required: false
   },
+  /**
+   * Types de soumission disponibles (enregistrer / enregistrer et quitter)
+   */
   submitActions: {
     type: Object,
     required: false,
@@ -111,203 +126,225 @@ const props = defineProps({
   }
 })
 
-const { i18n } = useNuxtApp()
+// ### Définitions
+
+const i18n = useI18n()
 const router = useRouter()
 const { em } = useEntityManager()
 
+// Le formulaire est-il valide
 const isValid: Ref<boolean> = ref(true)
+
+// Erreurs de validation
 const errors: Ref<Array<string>> = ref([])
 
-/**
- * Référence au component v-form
- */
+// Référence au component v-form
 const form: Ref = ref(null)
 
+// Le formulaire est-il en lecture seule
 const readonly: ComputedRef<boolean> = computed(() => {
   return useFormStore().readonly
 })
 
-/**
- * Utilise la méthode validate() de v-form pour valider le formulaire et mettre à jour les variables isValid et errors
- *
- * @see https://vuetifyjs.com/en/api/v-form/#functions-validate
- */
-const validate = async function () {
-  const validation = await form.value.validate()
-
-  isValid.value = validation.valid
-  errors.value = validation.errors
-}
+// La fenêtre de confirmation est-elle affichée
+const isConfirmationDialogShowing: ComputedRef<boolean> = computed(() => {
+  return useFormStore().showConfirmToLeave
+})
 
 /**
- * Handle events if the form is dirty to prevent submission
- * @param e
+ * Ferme la fenêtre de confirmation
  */
-// TODO: voir si encore nécessaire avec le @submit.prevent
-const preventSubmit = (e: any) => {
-  // Cancel the event
-  e.preventDefault()
-  // Chrome requires returnValue to be set
-  e.returnValue = ''
+const closeConfirmationDialog = () => {
+  useFormStore().setShowConfirmToLeave(false)
 }
 
+// ### Actions du formulaire
 /**
- * Définit l'état dirty (modifié) du formulaire
+ * Soumet le formulaire
+ *
+ * @param next
  */
-const setIsDirty = (dirty: boolean) => {
-  useFormStore().setDirty(dirty)
+const submit = async (next: string|null = null) => {
+  // Valide les données
+  await validate()
 
-  // If dirty, add the preventSubmit event listener
-  // TODO: voir si encore nécessaire avec le @submit.prevent
-  if (process.browser) {
-    if (dirty) {
-      window.addEventListener('beforeunload', preventSubmit)
-    } else {
-      window.removeEventListener('beforeunload', preventSubmit)
+  if (!isValid.value) {
+    usePageStore().addAlert(TYPE_ALERT.ALERT, ['invalid_form'])
+    return
+  }
+
+  try {
+    // TODO: est-ce qu'il faut re-fetch l'entité après le persist?
+    const updatedEntity = await em.persist(props.model, props.entity)
+
+    usePageStore().addAlert(TYPE_ALERT.SUCCESS, ['saveSuccess'])
+
+    // On retire l'état 'dirty'
+    setIsDirty(false)
+
+    afterSubmissionAction(next, updatedEntity)
+
+  } catch (error: any) {
+
+    if (error.response.status === 422 && error.response.data['violations']) {
+
+      // TODO: à revoir
+      const violations: Array<string> = []
+      let fields: AnyJson = {}
+
+      for (const violation of error.response.data['violations']) {
+        violations.push(i18n.t(violation['message']) as string)
+        fields = Object.assign(fields, {[violation['propertyPath']] : violation['message']})
+      }
+
+      useFormStore().addViolation(fields)
+
+      usePageStore().addAlert(TYPE_ALERT.ALERT, ['invalid_form'])
     }
   }
 }
 
-watch(props.entity, async (newEntity, oldEntity) => {
-  await onFormChange()
-})
-
 /**
- *  Update store when form is changed (if valid)
+ * Enregistre et quitte
  */
-const onFormChange = async () => {
-  console.log('form save')
+const saveAndQuit = async () => {
+  await submit()
+  quitForm()
+}
 
-  await validate()
+/**
+ * Retourne l'action à effectuer après la soumission du formulaire
+ * @param action
+ * @param updatedEntity
+ */
+const afterSubmissionAction = (action: string | null, updatedEntity: AnyJson) => {
+  if (action === null) {
+    return
+  }
 
-  if (isValid.value) {
-    em.save(props.model, props.entity)
-    setIsDirty(true)
+  const actionArgs = props.submitActions[action]
 
-    if (props.onChanged) {
-      // Execute the custom onChange method, if defined
-      // TODO: voir quelles variables passer à cette méthode custom ; d'ailleurs, vérifier aussi si cette méthode est utilisée
-      props.onChanged()
-    }
+  if (action === SUBMIT_TYPE.SAVE) {
+    afterSaveAction(actionArgs, updatedEntity.id, router)
+  } else if (action === SUBMIT_TYPE.SAVE_AND_BACK) {
+    afterSaveAndQuitAction(actionArgs, router)
   }
 }
 
-
-// <--- TODO: revoir les 4 méthodes qui suivent
 /**
- * Action Sauvegarder qui redirige vers la page d'édition si on est en mode create
+ * Après l'action Sauvegarder
+ *
+ * Si on était en mode édition, on reste sur cette page (on ne fait rien).
+ * Si on était en mode création, on bascule sur le mode édition
+ *
  * @param route
  * @param id
  * @param router
  */
-function save(route: Route, id: number, router: any){
-  if(useFormStore().formFunction === FORM_FUNCTION.CREATE){
+function afterSaveAction(route: Route, id: number, router: any){
+  if (useFormStore().formFunction === FORM_FUNCTION.CREATE) {
     route.path += id
     router.push(route)
   }
 }
 
 /**
- * Action sauvegarder et route suivante qui redirige vers une route
+ * Après l'action Sauvegarder et Quitter
+ *
+ * On redirige vers la route donnée
+ *
  * @param route
  * @param router
  */
-function saveAndGoTo(route: Route, router: any){
+function afterSaveAndQuitAction(route: Route, router: any){
   router.push(route)
 }
 
 /**
- * Factory des fonctions permettant d'assurer l'étape suivant à la soumission d'un formulaire
- *
- * @param args
- * @param response
- * @param router
+ * Quitte le formulaire sans enregistrer
  */
-function nextStepFactory(args: any, response: AnyJson, router: any){
-  const factory: AnyJson = {}
-  factory[SUBMIT_TYPE.SAVE] = () => save(args, response.id, router)
-  factory[SUBMIT_TYPE.SAVE_AND_BACK] = () => saveAndGoTo(args, router)
-  return factory
-}
+const quitForm = () => {
+  setIsDirty(false)
 
-const nextStep = (next: string | null, response: AnyJson) => {
-  if (next === null)
-    return
-  nextStepFactory(props.submitActions[next], response, router)[next]()
-}
+  useFormStore().setShowConfirmToLeave(false)
+
+  em.reset(props.model, props.entity.value)
 
-// ---> Fin du todo
+  if (router) {
+    // @ts-ignore
+    router.push(useFormStore().goAfterLeave) // TODO: voir si on peut pas passer ça comme prop du component
+  }
+}
 
+const actions = computed(()=>{
+  return _.keys(props.submitActions)
+})
 
+// #### Validation et store
 /**
- * Soumet le formulaire
- *
- * @param next
+ *  Update store when form is changed (if valid)
  */
-const submit = async (next: string|null = null) => {
+const onFormChange = async () => {
   await validate()
 
-  if (!isValid.value) {
-    usePageStore().addAlerts(TYPE_ALERT.ALERT, ['invalid_form'])
-    return
-  }
-
-  setIsDirty(false)
-
-  try {
-    const updatedEntity = await em.persist(props.model, props.entity)
-
-    usePageStore().addAlerts(TYPE_ALERT.SUCCESS, ['saveSuccess'])
-
-    // nextStep(next, updatedEntity)
-
-  } catch (error: any) {
-
-    if (error.response.status === 422 && error.response.data['violations']) {
-        const violations: Array<string> = [] // TODO: cette variable est-elle utile?
-        let fields: AnyJson = {}
-
-        for (const violation of error.response.data['violations']) {
-          violations.push(i18n.t(violation['message']) as string)
-          fields = Object.assign(fields, {[violation['propertyPath']] : violation['message']})
-        }
-
-        useFormStore().addViolations(fields)
+  if (isValid.value) {
+    em.save(props.model, props.entity)
+    setIsDirty(true)
 
-        usePageStore().addAlerts(TYPE_ALERT.ALERT, ['invalid_form'])
+    if (props.onChanged) {
+      // Execute the custom onChange method, if defined
+      // TODO: voir quelles variables passer à cette méthode custom ; d'ailleurs, vérifier aussi si cette méthode est utilisée
+      props.onChanged()
     }
   }
 }
 
-const showDialog: ComputedRef<boolean> = computed(() => {
-  return useFormStore().showConfirmToLeave
-})
+/**
+ * Utilise la méthode validate() de v-form pour valider le formulaire et mettre à jour les variables isValid et errors
+ *
+ * @see https://vuetifyjs.com/en/api/v-form/#functions-validate
+ */
+const validate = async function () {
+  const validation = await form.value.validate()
 
-const closeDialog = () => {
-  useFormStore().setShowConfirmToLeave(false)
+  isValid.value = validation.valid
+  errors.value = validation.errors
 }
 
-const saveAndQuit = async () => {
-  await submit()
-  quitForm()
-}
 
-const quitForm = () => {
-  setIsDirty(false)
+// #### Gestion de l'état dirty
+watch(props.entity, async (newEntity, oldEntity) => {
+  await onFormChange()
+})
 
-  useFormStore().setShowConfirmToLeave(false)
+/**
+ * Handle events if the form is dirty to prevent submission
+ * @param e
+ */
+// TODO: voir si encore nécessaire avec le @submit.prevent
+const preventSubmit = (e: any) => {
+  // Cancel the event
+  e.preventDefault()
+  // Chrome requires returnValue to be set
+  e.returnValue = ''
+}
 
-  em.reset(props.model, props.entity.value)
+/**
+ * Applique ou retire l'état dirty (modifié) du formulaire
+ */
+const setIsDirty = (dirty: boolean) => {
+  useFormStore().setDirty(dirty)
 
-  if (router) {
-    // @ts-ignore
-    router.push(useFormStore().goAfterLeave)
+  // If dirty, add the preventSubmit event listener
+  // TODO: voir si encore nécessaire avec le @submit.prevent
+  if (process.browser) {
+    if (dirty) {
+      window.addEventListener('beforeunload', preventSubmit)
+    } else {
+      window.removeEventListener('beforeunload', preventSubmit)
+    }
   }
 }
-
-const actions = computed(()=>{
-  return _.keys(props.submitActions)
-})
 </script>
 
 <style scoped>

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

@@ -7,16 +7,15 @@ Case à cocher, à placer dans un composant `UiForm`
 <template>
   <v-container
     class="px-0"
-    fluid
+    :fluid="true"
   >
     <v-checkbox
       :model-value="modelValue"
-      :value="modelValue"
       :label="$t(fieldLabel)"
       :disabled="readonly"
       :error="error || !!fieldViolations"
       :error-messages="errorMessage || fieldViolations ? $t(fieldViolations) : ''"
-      @update:model-value="onUpdate($event)"
+      @update:model-value="onUpdate"
     />
   </v-container>
 </template>
@@ -56,7 +55,8 @@ const props = defineProps({
    */
   readonly: {
     type: Boolean,
-    required: false
+    required: false,
+    default: false
   },
   /**
    * Règles de validation
@@ -90,9 +90,9 @@ const fieldLabel: string = props.label ?? props.field
 
 const emit = defineEmits(['update:model-value'])
 
-const onUpdate = (event: string) => {
+const onUpdate = (event: boolean) => {
   updateViolationState(event)
-  emit('update:model-value', props.modelValue)
+  emit('update:model-value', event)
 }
 
 </script>

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

@@ -30,7 +30,7 @@ const props = defineProps({
    * v-model
    */
   modelValue: {
-    type: Boolean,
+    type: [String, Number],
     required: false
   },
   /**
@@ -100,7 +100,7 @@ const emit = defineEmits(['update:model-value'])
 
 const onUpdate = (event: string) => {
   updateViolationState(event)
-  emit('update:model-value', props.modelValue)
+  emit('update:model-value', event)
 }
 
 </script>

+ 5 - 1
lang/fr.json

@@ -603,5 +603,9 @@
   "residenceAreas": "Zones de résidence",
   "sms_option": "Option SMS",
   "super_admin": "Compte super-admin",
-  "an_error_happened": "Une erreur s'est produite"
+  "an_error_happened": "Une erreur s'est produite",
+  "your_opentalent_website_is": "Votre site Opentalent est",
+  "record_a_new_subdomain": "Enregistrer un nouveau sous-domaine",
+  "subdomains_history": "Historique de vos sous domaine(s)",
+  "desactivateOpentalentSiteWeb": "Désactiver le site opentalent"
 }