From 8da7628f9db0ef7059b7bc7eb7394de7bde66a2e Mon Sep 17 00:00:00 2001 From: Alec M Date: Tue, 27 Feb 2024 10:29:33 -0500 Subject: [PATCH 1/2] feat: Auto save questionnaire without prompt if no errors --- .../Questionnaire/UnsavedChangesDialog.tsx | 2 +- src/content/questionnaire/FormView.tsx | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/components/Questionnaire/UnsavedChangesDialog.tsx b/src/components/Questionnaire/UnsavedChangesDialog.tsx index d052a15e..d7c41af3 100644 --- a/src/components/Questionnaire/UnsavedChangesDialog.tsx +++ b/src/components/Questionnaire/UnsavedChangesDialog.tsx @@ -31,7 +31,7 @@ const UnsavedChangesDialog: FC = ({ title={title || "Unsaved Changes"} message={ message - || "You have unsaved changes. Your changes will be lost if you leave this section without saving. Do you want to save your data?" + || "Validation errors have been detected. Do you wish to save your changes or discard them before leaving this page?" } actions={( <> diff --git a/src/content/questionnaire/FormView.tsx b/src/content/questionnaire/FormView.tsx index 722b5611..6d28acd3 100644 --- a/src/content/questionnaire/FormView.tsx +++ b/src/content/questionnaire/FormView.tsx @@ -242,21 +242,37 @@ const FormView: FC = ({ section } : Props) => { if (formMode === "Unauthorized" && status === FormStatus.LOADED && authStatus === AuthStatus.LOADED) { return false; } - if (!readOnlyInputs && isDirty()) { - setBlockedNavigate(true); - return true; + if (!isDirty() || readOnlyInputs) { + return false; + } + + // If there are no validation errors, save form data without a prompt + const { ref } = refs.getFormObjectRef.current?.() || {}; + if (ref?.current?.checkValidity() === true) { + saveForm(); + return false; } - return false; + setBlockedNavigate(true); + return true; }); // Intercept browser navigation actions (e.g. closing the tab) with unsaved changes useEffect(() => { const unloadHandler = (event: BeforeUnloadEvent) => { - if (isDirty()) { - event.preventDefault(); - event.returnValue = 'You have unsaved form changes. Are you sure you want to leave?'; + if (!isDirty()) { + return; } + + // If there are no validation errors, save form data without a prompt + const { ref } = refs.getFormObjectRef.current?.() || {}; + if (ref?.current?.checkValidity() === true) { + saveForm(); + return; + } + + event.preventDefault(); + event.returnValue = 'You have unsaved form changes. Are you sure you want to leave?'; }; window.addEventListener('beforeunload', unloadHandler); From 5178bf937609efb7102fe93f8d9d10da6a678d52 Mon Sep 17 00:00:00 2001 From: Alec M Date: Tue, 27 Feb 2024 10:35:35 -0500 Subject: [PATCH 2/2] feat: Migrate GenericAlert to Notistack --- src/content/questionnaire/FormView.tsx | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/content/questionnaire/FormView.tsx b/src/content/questionnaire/FormView.tsx index 6d28acd3..4d3a95ed 100644 --- a/src/content/questionnaire/FormView.tsx +++ b/src/content/questionnaire/FormView.tsx @@ -8,13 +8,13 @@ import { import { isEqual, cloneDeep } from "lodash"; import { Alert, - AlertColor, Container, Divider, Stack, styled, } from "@mui/material"; import { LoadingButton } from "@mui/lab"; +import { useSnackbar } from 'notistack'; import { ReactComponent as ChevronLeft } from "../../assets/icons/chevron_left.svg"; import { ReactComponent as ChevronRight } from "../../assets/icons/chevron_right.svg"; import { @@ -34,7 +34,6 @@ import RejectFormDialog from "../../components/Questionnaire/RejectFormDialog"; import ApproveFormDialog from "../../components/Questionnaire/ApproveFormDialog"; import PageBanner from "../../components/PageBanner"; import bannerPng from "../../assets/banner/submission_banner.png"; -import GenericAlert from "../../components/GenericAlert"; import { Status as AuthStatus, useAuthContext, @@ -142,11 +141,6 @@ export type SaveForm = | { status: "success"; id: string } | { status: "failed"; errorMessage: string }; -type AlertState = { - message: string; - severity: AlertColor; -}; - type Props = { section?: string; }; @@ -159,6 +153,7 @@ type Props = { */ const FormView: FC = ({ section } : Props) => { const navigate = useNavigate(); + const { enqueueSnackbar } = useSnackbar(); const { status, data, setData, submitData, approveForm, inquireForm, rejectForm, reopenForm, reviewForm, error } = useFormContext(); const { user, status: authStatus } = useAuthContext(); const [activeSection, setActiveSection] = useState(validateSection(section) ? section : "A"); @@ -169,7 +164,6 @@ const FormView: FC = ({ section } : Props) => { const [openRejectDialog, setOpenRejectDialog] = useState(false); const [hasError, setHasError] = useState(false); const { formMode, readOnlyInputs } = useFormMode(); - const [changesAlert, setChangesAlert] = useState(null); const [allSectionsComplete, setAllSectionsComplete] = useState(false); const sectionKeys = Object.keys(map); @@ -182,7 +176,6 @@ const FormView: FC = ({ section } : Props) => { const lastSectionRef = useRef(null); const hasReopenedFormRef = useRef(false); const hasUpdatedReviewStatusRef = useRef(false); - const alertTimeoutRef = useRef(null); const refs = { saveFormRef: createRef(), @@ -523,16 +516,11 @@ const FormView: FC = ({ section } : Props) => { if (!isEqual(data.questionnaireData, newData) || error === ErrorCodes.DUPLICATE_STUDY_ABBREVIATION) { const res = await setData(newData); if (res?.status === "failed" && res?.errorMessage === ErrorCodes.DUPLICATE_STUDY_ABBREVIATION) { - setChangesAlert({ severity: "error", message: `The Study Abbreviation already existed in the system. Your changes were unable to be saved.` }); + enqueueSnackbar("The Study Abbreviation already existed in the system. Your changes were unable to be saved.", { variant: 'error' }); } else { - setChangesAlert({ severity: "success", message: `Your changes for the ${map[activeSection].title} section have been successfully saved.` }); + enqueueSnackbar(`Your changes for the ${map[activeSection].title} section have been successfully saved.`, { variant: 'success' }); } - if (alertTimeoutRef.current) { - clearTimeout(alertTimeoutRef.current); - } - alertTimeoutRef.current = setTimeout(() => setChangesAlert(null), 10000); - if (!blockedNavigate && res?.status === "success" && data["_id"] === "new" && res.id !== data?.['_id']) { // NOTE: This currently triggers a form data refetch, which is not ideal navigate(`/submission/${res.id}/${activeSection}`, { replace: true }); @@ -697,12 +685,6 @@ const FormView: FC = ({ section } : Props) => { return ( <> - - - {changesAlert?.message} - - -