From fdbe68e571b5e2af01b76e2d9143a1dd86fcfc7f Mon Sep 17 00:00:00 2001 From: qhanson55 Date: Fri, 2 Aug 2024 11:10:36 -0700 Subject: [PATCH] implemented autosave functionality, made as a composable for reusability, can be applied to any form with save draft capabilities --- .../housing/enquiry/EnquiryIntakeForm.vue | 21 ++++++-- .../submission/SubmissionIntakeForm.vue | 45 ++++++++++++----- frontend/src/composables/formAutoSave.ts | 49 +++++++++++++++++++ 3 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 frontend/src/composables/formAutoSave.ts diff --git a/frontend/src/components/housing/enquiry/EnquiryIntakeForm.vue b/frontend/src/components/housing/enquiry/EnquiryIntakeForm.vue index 8f1943cb..82ee1f5f 100644 --- a/frontend/src/components/housing/enquiry/EnquiryIntakeForm.vue +++ b/frontend/src/components/housing/enquiry/EnquiryIntakeForm.vue @@ -5,10 +5,11 @@ import { onBeforeMount, ref, toRaw } from 'vue'; import { useRouter } from 'vue-router'; import { object, string } from 'yup'; -import { Dropdown, InputMask, RadioList, InputText, StepperNavigation, TextArea } from '@/components/form'; +import { Dropdown, InputMask, InputText, RadioList, StepperNavigation, TextArea } from '@/components/form'; import CollectionDisclaimer from '@/components/housing/CollectionDisclaimer.vue'; import EnquiryIntakeConfirmation from '@/components/housing/enquiry/EnquiryIntakeConfirmation.vue'; import { Button, Card, Divider, useConfirm, useToast } from '@/lib/primevue'; +import { useAutoSave } from '@/composables/formAutoSave'; import { activityService, enquiryService, submissionService } from '@/services'; import { useConfigStore } from '@/store'; import { ACTIVITY_ID_LENGTH, YES_NO_LIST } from '@/utils/constants/application'; @@ -19,6 +20,13 @@ import { confirmationTemplate } from '@/utils/templates'; import type { Ref } from 'vue'; +const { formUpdated, stopAutoSave } = useAutoSave(async () => { + const values = formRef.value?.values; + if (values) { + await onSaveDraft(values, true); + } +}); + // Props type Props = { activityId?: string; @@ -115,7 +123,7 @@ function onInvalidSubmit(e: any) { document.getElementById('form')?.scrollIntoView({ behavior: 'smooth' }); } -async function onSaveDraft(data: any) { +async function onSaveDraft(data: any, isAutoSave = false) { editable.value = false; try { @@ -133,7 +141,12 @@ async function onSaveDraft(data: any) { throw new Error('Failed to retrieve correct draft data'); } - toast.success('Draft saved'); + if (isAutoSave) { + toast.success('Draft autosaved'); + } else { + toast.success('Draft saved'); + formUpdated.value = false; + } } catch (e: any) { toast.error('Failed to save draft', e); } finally { @@ -169,6 +182,7 @@ async function onSubmit(data: any) { formRef.value?.setFieldValue('enquiryId', enquiryResponse.data.enquiryId); // Send confirmation email emailConfirmation(enquiryResponse.data.activityId); + stopAutoSave(); } else { throw new Error('Failed to retrieve correct enquiry draft data'); } @@ -278,6 +292,7 @@ async function checkActivityIdValidity(event: Event) { :validation-schema="formSchema" @invalid-submit="(e) => onInvalidSubmit(e)" @submit="confirmSubmit" + @change="formUpdated = true" > > = ref([]); const parcelAccordionIndex: Ref = ref(undefined); const spacialAccordionIndex: Ref = ref(undefined); const validationErrors: Ref = ref([]); -const formUpdated: Ref = ref(false); +const formModified: Ref = ref(false); + +const { formUpdated, stopAutoSave } = useAutoSave(() => { + const values = formRef.value?.values; + if (values) onSaveDraft(values, true); +}); // Actions const confirm = useConfirm(); @@ -174,7 +180,7 @@ const onLatLongInputClick = async () => { function onInvalidSubmit(e: any) { validationErrors.value = Array.from(new Set(e.errors ? Object.keys(e.errors).map((x) => x.split('.')[0]) : [])); document.getElementById('form')?.scrollIntoView({ behavior: 'smooth' }); - formUpdated.value = false; + formModified.value = false; } function onPermitsHasAppliedChange(e: BasicResponse, fieldsLength: number, push: Function, setFieldValue: Function) { @@ -191,7 +197,7 @@ function onPermitsHasAppliedChange(e: BasicResponse, fieldsLength: number, push: } } -async function onSaveDraft(data: any) { +async function onSaveDraft(data: any, isAutoSave = false) { editable.value = false; const tempData = Object.assign({}, data); @@ -212,7 +218,12 @@ async function onSaveDraft(data: any) { throw new Error('Failed to retrieve correct draft data'); } - toast.success('Draft saved'); + if (isAutoSave) { + toast.success('Draft autosaved'); + } else { + toast.success('Draft saved'); + formUpdated.value = false; + } } catch (e: any) { toast.error('Failed to save draft', e); } finally { @@ -235,6 +246,7 @@ async function onSubmit(data: any) { formRef.value?.setFieldValue('activityId', response.data.activityId); // Send confirmation email emailConfirmation(response.data.activityId); + stopAutoSave(); } else { throw new Error('Failed to retrieve correct draft data'); } @@ -370,7 +382,12 @@ onBeforeMount(async () => { :validation-schema="submissionIntakeSchema" @invalid-submit="(e) => onInvalidSubmit(e)" @submit="confirmSubmit" - @change="formUpdated = true" + @change=" + () => { + formUpdated = true; + formModified = true; + } + " > { 'app-error-color': (validationErrors.includes(IntakeFormCategory.APPLICANT) || validationErrors.includes(IntakeFormCategory.BASIC)) && - !formUpdated + !formModified }" /> @@ -415,7 +432,7 @@ onBeforeMount(async () => { { :click-callback="clickCallback" title="Housing" icon="fa-house" - :class="{ 'app-error-color': validationErrors.includes(IntakeFormCategory.HOUSING) && !formUpdated }" + :class="{ + 'app-error-color': validationErrors.includes(IntakeFormCategory.HOUSING) && !formModified + }" />