From ce70ea455a374dbb3c262884459645f6089b583d Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Tue, 3 Dec 2024 10:56:32 +0100 Subject: [PATCH] Use global form is dirty --- .../src/pages/detail/FormDirtyContext.tsx | 20 ++++ src/client/src/pages/detail/detailHeader.tsx | 4 +- src/client/src/pages/detail/detailPage.tsx | 102 +++++++++--------- .../src/pages/detail/detailPageContent.tsx | 4 - .../detail/form/borehole/boreholePanel.tsx | 3 +- .../form/borehole/boreholePanelInterfaces.ts | 1 - .../detail/form/location/locationPanel.tsx | 3 +- .../form/location/locationPanelInterfaces.tsx | 1 - .../pages/detail/form/useFormWithSaveBar.ts | 11 +- src/client/src/pages/detail/saveBar.tsx | 5 +- .../src/pages/detail/useBlockNavigation.tsx | 4 +- 11 files changed, 85 insertions(+), 73 deletions(-) create mode 100644 src/client/src/pages/detail/FormDirtyContext.tsx diff --git a/src/client/src/pages/detail/FormDirtyContext.tsx b/src/client/src/pages/detail/FormDirtyContext.tsx new file mode 100644 index 000000000..08ec2a1a3 --- /dev/null +++ b/src/client/src/pages/detail/FormDirtyContext.tsx @@ -0,0 +1,20 @@ +import React, { createContext, useContext, useState } from "react"; + +interface FormDirtyContextType { + isFormDirty: boolean; + setIsFormDirty: (dirty: boolean) => void; +} + +const FormDirtyContext = createContext(undefined); + +export const FormDirtyProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [isFormDirty, setIsFormDirty] = useState(false); + + return {children}; +}; + +export const useFormDirty = () => { + const context = useContext(FormDirtyContext); + if (!context) throw new Error("useFormDirty must be used within a FormDirtyProvider"); + return context; +}; diff --git a/src/client/src/pages/detail/detailHeader.tsx b/src/client/src/pages/detail/detailHeader.tsx index 80f02a000..995f7bbb2 100644 --- a/src/client/src/pages/detail/detailHeader.tsx +++ b/src/client/src/pages/detail/detailHeader.tsx @@ -17,13 +17,13 @@ import { import DateText from "../../components/legacyComponents/dateText"; import { PromptContext } from "../../components/prompt/promptContext.tsx"; import { DetailHeaderStack } from "../../components/styledComponents.ts"; +import { useFormDirty } from "./FormDirtyContext.tsx"; interface DetailHeaderProps { editingEnabled: boolean; setEditingEnabled: (editingEnabled: boolean) => void; editableByCurrentUser: boolean; borehole: BoreholeV2; - isFormDirty: boolean; triggerReset: () => void; } @@ -31,7 +31,6 @@ const DetailHeader = ({ editingEnabled, setEditingEnabled, editableByCurrentUser, - isFormDirty, triggerReset, borehole, }: DetailHeaderProps) => { @@ -39,6 +38,7 @@ const DetailHeader = ({ const dispatch = useDispatch(); const { t } = useTranslation(); const { showPrompt } = useContext(PromptContext); + const { isFormDirty } = useFormDirty(); const auth = useAuth(); const toggleEditing = (editing: boolean) => { diff --git a/src/client/src/pages/detail/detailPage.tsx b/src/client/src/pages/detail/detailPage.tsx index 8ab2023e3..67455ac25 100644 --- a/src/client/src/pages/detail/detailPage.tsx +++ b/src/client/src/pages/detail/detailPage.tsx @@ -16,6 +16,7 @@ import { DetailPageContent } from "./detailPageContent.tsx"; import { DetailSideNav } from "./detailSideNav.tsx"; import { BoreholeFormInputs } from "./form/borehole/boreholePanelInterfaces.ts"; import { LocationFormInputs, LocationFormSubmission } from "./form/location/locationPanelInterfaces.tsx"; +import { FormDirtyProvider } from "./FormDirtyContext.tsx"; import { useLabelingContext } from "./labeling/labelingInterfaces.tsx"; import LabelingPanel from "./labeling/labelingPanel.tsx"; import { SaveBar } from "./saveBar"; @@ -23,7 +24,6 @@ import { SaveBar } from "./saveBar"; export const DetailPage: FC = () => { const [editingEnabled, setEditingEnabled] = useState(false); const [loading, setLoading] = useState(true); - const [isFormDirty, setIsFormDirty] = useState(false); const [editableByCurrentUser, setEditableByCurrentUser] = useState(false); const [borehole, setBorehole] = useState(null); const legacyBorehole: Borehole = useSelector((state: ReduxRootState) => state.core_borehole); @@ -76,10 +76,6 @@ export const DetailPage: FC = () => { getAndUpdateBorehole(prepareLocationDataForSubmit(formInputs)); }; - const handleDirtyChange = (isDirty: boolean) => { - setIsFormDirty(isDirty); - }; - const triggerSubmit = () => { boreholePanelRef.current?.submit(); locationPanelRef.current?.submit(); @@ -133,58 +129,58 @@ export const DetailPage: FC = () => { return ( <> - - - - - - - - + + + + + + + - {editingEnabled && ( - + {editingEnabled && ( + togglePanel()} + /> + )} + togglePanel()} /> - )} - - - {editingEnabled && panelOpen && } - - {editingEnabled && shouldShowSaveBar && ( - - )} - - + + {editingEnabled && panelOpen && } + + {editingEnabled && shouldShowSaveBar && ( + + )} + + + ); }; diff --git a/src/client/src/pages/detail/detailPageContent.tsx b/src/client/src/pages/detail/detailPageContent.tsx index f9aa67307..4bf0ec86c 100644 --- a/src/client/src/pages/detail/detailPageContent.tsx +++ b/src/client/src/pages/detail/detailPageContent.tsx @@ -29,7 +29,6 @@ interface DetailPageContentProps { boreholePanelRef: RefObject<{ submit: () => void; reset: () => void }>; onLocationFormSubmit: (data: LocationFormInputs) => void; onBoreholeFormSubmit: (data: BoreholeFormInputs) => void; - handleDirtyChange: (isDirty: boolean) => void; borehole: BoreholeV2; panelOpen: boolean; } @@ -44,7 +43,6 @@ export const DetailPageContent = ({ boreholePanelRef, onLocationFormSubmit, onBoreholeFormSubmit, - handleDirtyChange, borehole, panelOpen, }: DetailPageContentProps) => { @@ -101,7 +99,6 @@ export const DetailPageContent = ({ editingEnabled={editingEnabled} onSubmit={onLocationFormSubmit} borehole={borehole} - onDirtyChange={handleDirtyChange} labelingPanelOpen={panelOpen} /> )} @@ -116,7 +113,6 @@ export const DetailPageContent = ({ boreholeId={id} borehole={borehole} editingEnabled={editingEnabled} - onDirtyChange={handleDirtyChange} /> )} /> diff --git a/src/client/src/pages/detail/form/borehole/boreholePanel.tsx b/src/client/src/pages/detail/form/borehole/boreholePanel.tsx index 334217dbe..6bafa8147 100644 --- a/src/client/src/pages/detail/form/borehole/boreholePanel.tsx +++ b/src/client/src/pages/detail/form/borehole/boreholePanel.tsx @@ -11,7 +11,7 @@ import Geometry from "./geometry.jsx"; import Sections from "./sections.jsx"; export const BoreholePanel = forwardRef( - ({ boreholeId, borehole, editingEnabled, onDirtyChange, onSubmit }: BoreholePanelProps, ref) => { + ({ boreholeId, borehole, editingEnabled, onSubmit }: BoreholePanelProps, ref) => { const { t } = useTranslation(); const history = useHistory(); const location = useLocation(); @@ -36,7 +36,6 @@ export const BoreholePanel = forwardRef( UseFormWithSaveBar({ formMethods, - onDirtyChange, onSubmit, ref, }); diff --git a/src/client/src/pages/detail/form/borehole/boreholePanelInterfaces.ts b/src/client/src/pages/detail/form/borehole/boreholePanelInterfaces.ts index 556632a20..9dbcfe07a 100644 --- a/src/client/src/pages/detail/form/borehole/boreholePanelInterfaces.ts +++ b/src/client/src/pages/detail/form/borehole/boreholePanelInterfaces.ts @@ -13,7 +13,6 @@ export interface BoreholeDetailProps extends BoreholeGeneralProps { export interface BoreholePanelProps extends BoreholeGeneralProps { boreholeId: string; onSubmit: (data: BoreholeFormInputs) => void; - onDirtyChange: (isDirty: boolean) => void; } export interface BoreholeFormInputs { diff --git a/src/client/src/pages/detail/form/location/locationPanel.tsx b/src/client/src/pages/detail/form/location/locationPanel.tsx index 34f01ea34..f67723e56 100644 --- a/src/client/src/pages/detail/form/location/locationPanel.tsx +++ b/src/client/src/pages/detail/form/location/locationPanel.tsx @@ -10,7 +10,7 @@ import NameSegment from "./nameSegment.tsx"; import RestrictionSegment from "./restrictionSegment.tsx"; export const LocationPanel = forwardRef( - ({ editingEnabled, onSubmit, onDirtyChange, borehole, labelingPanelOpen }: LocationPanelProps, ref) => { + ({ editingEnabled, onSubmit, borehole, labelingPanelOpen }: LocationPanelProps, ref) => { const [resetKey, setResetKey] = useState(0); const formMethods = useForm({ mode: "onChange", @@ -44,7 +44,6 @@ export const LocationPanel = forwardRef( UseFormWithSaveBar({ formMethods, - onDirtyChange, onSubmit, ref, incrementResetKey, diff --git a/src/client/src/pages/detail/form/location/locationPanelInterfaces.tsx b/src/client/src/pages/detail/form/location/locationPanelInterfaces.tsx index 5017ad2c2..34b1895c9 100644 --- a/src/client/src/pages/detail/form/location/locationPanelInterfaces.tsx +++ b/src/client/src/pages/detail/form/location/locationPanelInterfaces.tsx @@ -9,7 +9,6 @@ export interface LocationBaseProps { export interface LocationPanelProps extends LocationBaseProps { onSubmit: (data: LocationFormInputs) => void; - onDirtyChange: (isDirty: boolean) => void; labelingPanelOpen: boolean; ref: RefObject<{ submit: () => void; reset: () => void }>; } diff --git a/src/client/src/pages/detail/form/useFormWithSaveBar.ts b/src/client/src/pages/detail/form/useFormWithSaveBar.ts index fdd2edb40..07b7b628d 100644 --- a/src/client/src/pages/detail/form/useFormWithSaveBar.ts +++ b/src/client/src/pages/detail/form/useFormWithSaveBar.ts @@ -1,11 +1,11 @@ import { ForwardedRef, useCallback, useEffect, useImperativeHandle } from "react"; import { FieldValues, UseFormReturn } from "react-hook-form"; import { useHistory } from "react-router-dom"; +import { useFormDirty } from "../FormDirtyContext.tsx"; import { useBlockNavigation } from "../useBlockNavigation.tsx"; interface UseFormWithSaveBarProps { formMethods: UseFormReturn; - onDirtyChange: (isDirty: boolean) => void; onSubmit: (data: T) => void; ref: ForwardedRef; incrementResetKey?: () => void; @@ -13,13 +13,13 @@ interface UseFormWithSaveBarProps { export function UseFormWithSaveBar({ formMethods, - onDirtyChange, onSubmit, incrementResetKey, ref, }: UseFormWithSaveBarProps) { const history = useHistory(); - const { handleBlockedNavigation } = useBlockNavigation(formMethods.formState.isDirty); + const { handleBlockedNavigation } = useBlockNavigation(); + const { setIsFormDirty } = useFormDirty(); // Block navigation if form is dirty history.block(nextLocation => { @@ -30,13 +30,14 @@ export function UseFormWithSaveBar({ // Track form dirty state useEffect(() => { - onDirtyChange(Object.keys(formMethods.formState.dirtyFields).length > 0); + setIsFormDirty(Object.keys(formMethods.formState.dirtyFields).length > 0); + return () => setIsFormDirty(false); }, [ formMethods.formState.dirtyFields, formMethods.formState.isDirty, formMethods, formMethods.formState, - onDirtyChange, + setIsFormDirty, ]); // Handle form reset and submit diff --git a/src/client/src/pages/detail/saveBar.tsx b/src/client/src/pages/detail/saveBar.tsx index 8a97ccc19..eaaeb48f7 100644 --- a/src/client/src/pages/detail/saveBar.tsx +++ b/src/client/src/pages/detail/saveBar.tsx @@ -5,16 +5,17 @@ import { Box, Stack } from "@mui/material"; import { CircleCheck, CircleX } from "lucide-react"; import { theme } from "../../AppTheme.ts"; import { DeleteButton, SaveButton } from "../../components/buttons/buttons.tsx"; +import { useFormDirty } from "./FormDirtyContext.tsx"; interface SaveBarProps { triggerSubmit: () => void; triggerReset: () => void; - isFormDirty: boolean; } -export const SaveBar = ({ triggerSubmit, triggerReset, isFormDirty }: SaveBarProps) => { +export const SaveBar = ({ triggerSubmit, triggerReset }: SaveBarProps) => { const [showSaveFeedback, setShowSaveFeedback] = useState(false); const { t } = useTranslation(); const location = useLocation(); + const { isFormDirty } = useFormDirty(); useEffect(() => { setShowSaveFeedback(false); diff --git a/src/client/src/pages/detail/useBlockNavigation.tsx b/src/client/src/pages/detail/useBlockNavigation.tsx index 197b98a5d..e46b7552c 100644 --- a/src/client/src/pages/detail/useBlockNavigation.tsx +++ b/src/client/src/pages/detail/useBlockNavigation.tsx @@ -3,14 +3,16 @@ import { useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; import { Trash2, X } from "lucide-react"; import { PromptContext } from "../../components/prompt/promptContext.tsx"; +import { useFormDirty } from "./FormDirtyContext.tsx"; interface UseBlockNavigationResult { handleBlockedNavigation: (nextLocation: string) => boolean; } -export const useBlockNavigation = (isFormDirty: boolean): UseBlockNavigationResult => { +export const useBlockNavigation = (): UseBlockNavigationResult => { const [nextLocation, setNextLocation] = useState(null); const [confirmedNavigation, setConfirmedNavigation] = useState(false); + const { isFormDirty } = useFormDirty(); const { showPrompt } = useContext(PromptContext); const { t } = useTranslation(); const history = useHistory();