From 357d6bf7fd541d8049b8eb5d9f36586acca9d164 Mon Sep 17 00:00:00 2001 From: benjaminjohnson2204 Date: Tue, 28 May 2024 13:40:10 -0700 Subject: [PATCH] Display confirmation dialog if user has unsaved changes --- .../src/app/staff/furnitureItems/page.tsx | 1 - frontend/src/app/vsr/page.tsx | 5 +++- .../FurnitureRequest/EditTemplate/index.tsx | 15 ++++++---- .../VSRIndividual/VSRIndividualPage/index.tsx | 30 +++++++++++++++---- frontend/src/hooks/useDirtyForm.ts | 30 +++++++++++++++++++ 5 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 frontend/src/hooks/useDirtyForm.ts diff --git a/frontend/src/app/staff/furnitureItems/page.tsx b/frontend/src/app/staff/furnitureItems/page.tsx index bd99a29..d9c51b4 100644 --- a/frontend/src/app/staff/furnitureItems/page.tsx +++ b/frontend/src/app/staff/furnitureItems/page.tsx @@ -45,7 +45,6 @@ export default function furnitureItemTemplate() { const handleBeginEditing = (category: string) => { setEditingCategory(category); - console.log(category); }; const handleFinishEditing = () => { diff --git a/frontend/src/app/vsr/page.tsx b/frontend/src/app/vsr/page.tsx index fc41e66..e1034a3 100644 --- a/frontend/src/app/vsr/page.tsx +++ b/frontend/src/app/vsr/page.tsx @@ -35,6 +35,7 @@ import { ICreateVSRFormInput, IVSRFormInput } from "@/components/VSRForm/VSRForm import { vsrInputFieldValidators } from "@/components/VSRForm/VSRFormValidators"; import { ListDetail, SingleDetail } from "@/components/VSRIndividual"; import styles from "@/app/vsr/page.module.css"; +import { useDirtyForm } from "@/hooks/useDirtyForm"; enum VSRFormError { CANNOT_RETRIEVE_FURNITURE_NO_INTERNET, @@ -56,11 +57,13 @@ const VeteranServiceRequest: React.FC = () => { register, handleSubmit, control, - formState: { errors, isValid }, + formState: { errors, isValid, dirtyFields }, watch, reset, } = formProps; + useDirtyForm({ isDirty: Object.keys(dirtyFields).length > 0 }); + /** * Internal state for fields that are complicated and cannot be controlled with a * named form field alone (e.g. there is a multiple choice and a text field for "Other") diff --git a/frontend/src/components/FurnitureRequest/EditTemplate/index.tsx b/frontend/src/components/FurnitureRequest/EditTemplate/index.tsx index a15c500..4dd6158 100644 --- a/frontend/src/components/FurnitureRequest/EditTemplate/index.tsx +++ b/frontend/src/components/FurnitureRequest/EditTemplate/index.tsx @@ -17,6 +17,7 @@ import { ConfirmDeleteModal } from "@/components/shared/ConfirmDeleteModal"; import { Button } from "@/components/shared/Button"; import { NotificationBanner } from "@/components/shared/NotificationBanner"; import { ConfirmDiscardEditsModal } from "@/components/shared/ConfirmDiscardEditsModal"; +import { useDirtyForm } from "@/hooks/useDirtyForm"; enum FurnitureItemAction { NONE, @@ -178,6 +179,13 @@ export const EditTemplate = ({ const canSelectAnotherItem = isEditing && !isAddingNewItem && !editingItemId; + const hasUnsavedChanges = + (isAddingNewItem && (itemName !== "" || allowMultiple)) || + (editingItemId !== null && + (itemName !== getFurnitureItemById(editingItemId)?.name || + allowMultiple !== getFurnitureItemById(editingItemId)?.allowMultiple)); + useDirtyForm({ isDirty: hasUnsavedChanges }); + return ( <>
@@ -264,12 +272,7 @@ export const EditTemplate = ({ variant="error" outlined onClick={() => { - if ( - (isAddingNewItem && (itemName || allowMultiple)) || - (editingItemId && - (itemName !== getFurnitureItemById(editingItemId)?.name || - allowMultiple !== getFurnitureItemById(editingItemId)?.allowMultiple)) - ) { + if (hasUnsavedChanges) { setDiscardEditsConfirmationModalOpen(true); } else { onFinishEditing(); diff --git a/frontend/src/components/VSRIndividual/VSRIndividualPage/index.tsx b/frontend/src/components/VSRIndividual/VSRIndividualPage/index.tsx index c9db9a5..ef16252 100644 --- a/frontend/src/components/VSRIndividual/VSRIndividualPage/index.tsx +++ b/frontend/src/components/VSRIndividual/VSRIndividualPage/index.tsx @@ -34,6 +34,7 @@ import { useMediaQuery } from "@mui/material"; import styles from "@/components/VSRIndividual/VSRIndividualPage/styles.module.css"; import { ConfirmDiscardEditsModal } from "@/components/shared/ConfirmDiscardEditsModal"; import { ADMIN_ROLE } from "@/constants/roles"; +import { useDirtyForm } from "@/hooks/useDirtyForm"; enum VSRIndividualError { CANNOT_RETRIEVE_FURNITURE_NO_INTERNET, @@ -60,7 +61,14 @@ export const VSRIndividualPage = () => { const [isEditing, setIsEditing] = useState(false); const formProps = useForm(); - const { handleSubmit } = formProps; + const { + handleSubmit, + formState: { dirtyFields }, + reset, + } = formProps; + + const isDirty = Object.keys(dirtyFields).length > 0; + useDirtyForm({ isDirty }); const [updateStatusSuccessNotificationOpen, setUpdateStatusSuccessNotificationOpen] = useState(false); @@ -148,6 +156,7 @@ export const VSRIndividualPage = () => { // Handle success/error if (response.success) { + reset(); setIsEditing(false); setVSR(response.data); setEditSuccessNotificationOpen(true); @@ -277,6 +286,12 @@ export const VSRIndividualPage = () => { setLoadingDownload(false); }; + const discardChanges = () => { + reset(); + fetchVSR(); + setIsEditing(false); + }; + /** * Conditionally renders the "Approve" button on the page, if the VSR's status is "Received" */ @@ -312,7 +327,13 @@ export const VSRIndividualPage = () => { iconAlt="Close" text="Discard Changes" hideTextOnMobile - onClick={() => setDiscardEditsConfirmationModalOpen(true)} + onClick={() => { + if (isDirty) { + setDiscardEditsConfirmationModalOpen(true); + } else { + discardChanges(); + } + }} />