diff --git a/changelog/4419.added.md b/changelog/4419.added.md new file mode 100644 index 0000000000..bb8a830a81 --- /dev/null +++ b/changelog/4419.added.md @@ -0,0 +1 @@ +* Prevent the form from being closed if there are unsaved changes. \ No newline at end of file diff --git a/frontend/app/.eslintrc b/frontend/app/.eslintrc index 6982c2cd60..81da3d72ac 100644 --- a/frontend/app/.eslintrc +++ b/frontend/app/.eslintrc @@ -25,6 +25,7 @@ "tsconfig.json" ], "rules": { + "react/prop-types": "off", "quotes": [ "error", "double", @@ -74,4 +75,4 @@ "semi": "error", "no-trailing-spaces": "error" } -} +} \ No newline at end of file diff --git a/frontend/app/src/components/display/slide-over.tsx b/frontend/app/src/components/display/slide-over.tsx index 84718f705d..e9990959cb 100644 --- a/frontend/app/src/components/display/slide-over.tsx +++ b/frontend/app/src/components/display/slide-over.tsx @@ -1,11 +1,12 @@ import { Dialog, Transition } from "@headlessui/react"; -import React, { Fragment, useRef } from "react"; +import React, { Fragment, useRef, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Icon } from "@iconify-icon/react"; import { ObjectHelpButton } from "@/components/menu/object-help-button"; import { useAtomValue } from "jotai/index"; import { currentBranchAtom } from "@/state/atoms/branches.atom"; import { IModelSchema } from "@/state/atoms/schema.atom"; +import ModalDelete from "../modals/modal-delete"; interface Props { open: boolean; @@ -15,9 +16,16 @@ interface Props { offset?: number; } +interface SlideOverContextProps { + setPreventClose?: (value: boolean) => void; +} + +export const SlideOverContext = React.createContext({}); + export default function SlideOver(props: Props) { const { open, setOpen, title, offset = 0 } = props; const initialFocusRef = useRef(null); + const [preventClose, setPreventClose] = useState(false); // Need to define full classes so tailwind can compile the css const panelWidth = "w-[400px]"; @@ -27,52 +35,69 @@ export default function SlideOver(props: Props) { 1: "-translate-x-[400px]", }; + const context = { + isOpen: open || (!open && preventClose), + setPreventClose: (value: boolean) => setPreventClose(value), + }; + return ( - - - -
- - -
-
-
-
- {props.children} - - + {props.children} + + +
- -
-
+ + + + setOpen(true)} + onDelete={() => setPreventClose(false)} + open={!open && preventClose} + setOpen={() => setPreventClose(false)} + confirmLabel="Close" + /> + ); } diff --git a/frontend/app/src/components/modals/modal-confirm.tsx b/frontend/app/src/components/modals/modal-confirm.tsx index a8747eafa0..fcee449c14 100644 --- a/frontend/app/src/components/modals/modal-confirm.tsx +++ b/frontend/app/src/components/modals/modal-confirm.tsx @@ -9,10 +9,10 @@ interface iProps { isLoading?: boolean; setOpen: React.Dispatch>; title: string; - description: string | React.ReactNode; + description?: string | React.ReactNode; onConfirm: Function; onCancel: Function; - children: ReactNode; + children?: ReactNode; icon?: string; } diff --git a/frontend/app/src/components/ui/form.tsx b/frontend/app/src/components/ui/form.tsx index c61773fe0b..fec4d1d41d 100644 --- a/frontend/app/src/components/ui/form.tsx +++ b/frontend/app/src/components/ui/form.tsx @@ -20,6 +20,7 @@ import { } from "react-hook-form"; import { Spinner } from "@/components/ui/spinner"; import Label, { LabelProps } from "@/components/ui/label"; +import { SlideOverContext } from "../display/slide-over"; export type FormRef = ReturnType; @@ -33,12 +34,23 @@ export const Form = React.forwardRef( ({ form, defaultValues, className, children, onSubmit, ...props }: FormProps, ref) => { const currentForm = form ?? useForm({ defaultValues }); + const slideOverContext = useContext(SlideOverContext); + useImperativeHandle(ref, () => currentForm); useEffect(() => { currentForm.reset(defaultValues); }, [JSON.stringify(defaultValues)]); + useEffect(() => { + // Stop logic if there is no context to prevent the slide over close + if (!slideOverContext?.setPreventClose) return; + + if (!currentForm.formState.isDirty) return; + + slideOverContext?.setPreventClose(true); + }, [currentForm.formState.isDirty]); + return (
( event.stopPropagation(); } - if (!onSubmit) return; + if (onSubmit) currentForm.handleSubmit(onSubmit)(event); - currentForm.handleSubmit(onSubmit)(event); + if (slideOverContext?.setPreventClose) { + slideOverContext?.setPreventClose(false); + } }} className={classNames("space-y-4", className)} {...props}> diff --git a/frontend/app/src/hooks/usePagination.ts b/frontend/app/src/hooks/usePagination.ts index 2608efd535..f646c4f0f4 100644 --- a/frontend/app/src/hooks/usePagination.ts +++ b/frontend/app/src/hooks/usePagination.ts @@ -53,9 +53,12 @@ const usePagination = (): [tPagination, Function] => { // Set the pagination in the QSP const setPagination = (newPagination: tPagination) => { + const newLimit = getVerifiedLimit(newPagination?.limit, config); + const newOffset = getVerifiedOffset(newPagination?.offset, config); + const newValidatedPagination = { - limit: getVerifiedLimit(newPagination?.limit, config), - offset: getVerifiedOffset(newPagination?.offset, config), + limit: newLimit, + offset: newOffset, }; setPaginationInQueryString(JSON.stringify(newValidatedPagination));