From f66ab92466c63fba3e1d7f792516db86e5c991bb Mon Sep 17 00:00:00 2001 From: Jeffrin Jojo <135723871+Jeffrin2005@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:11:27 +0530 Subject: [PATCH 1/9] Fixed Submit button issue, Closes #9501 --- package-lock.json | 10 ++++++++-- package.json | 2 ++ src/components/Form/Form.tsx | 16 +++++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b13986a77b..03f54c64746 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@sentry/browser": "^8.45.1", "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.7", + "@types/lodash": "^4.17.13", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", @@ -49,6 +50,7 @@ "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.2", "i18next-http-backend": "^3.0.1", + "lodash": "^4.17.21", "postcss-loader": "^8.1.1", "qrcode.react": "^4.1.0", "raviger": "^4.1.2", @@ -6122,6 +6124,11 @@ "optional": true, "peer": true }, + "node_modules/@types/lodash": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==" + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -12882,8 +12889,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.castarray": { "version": "4.4.0", diff --git a/package.json b/package.json index 2f53967d1e6..7440d1d6171 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@sentry/browser": "^8.45.1", "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.7", + "@types/lodash": "^4.17.13", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", @@ -88,6 +89,7 @@ "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.2", "i18next-http-backend": "^3.0.1", + "lodash": "^4.17.21", "postcss-loader": "^8.1.1", "qrcode.react": "^4.1.0", "raviger": "^4.1.2", diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 18f190ccc3c..db867919e99 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,3 +1,4 @@ +import isEqual from "lodash/isEqual"; import { useEffect, useMemo, useRef, useState } from "react"; import { Cancel, Submit } from "@/components/Common/ButtonV2"; @@ -47,6 +48,7 @@ const Form = ({ const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); const [state, dispatch] = useAutoSaveReducer(formReducer, initial); const formVals = useRef(props.defaults); + const [isFormModified, setIsFormModified] = useState(false); // Add this line useEffect(() => { if (!asyncGetDefaults) return; @@ -82,12 +84,14 @@ const Form = ({ }); } else if (props.resetFormValsOnSubmit) { dispatch({ type: "set_form", form: formVals.current }); + setIsFormModified(false); } }; const handleCancel = () => { if (props.resetFormValsOnCancel) { dispatch({ type: "set_form", form: formVals.current }); + setIsFormModified(false); } props.onCancel?.(); }; @@ -123,13 +127,19 @@ const Form = ({ return { name, id: name, - onChange: ({ name, value }: FieldChangeEvent) => + onChange: ({ name, value }: FieldChangeEvent) => { + const newForm = { + ...state.form, + [name]: value, + }; + setIsFormModified(!isEqual(newForm, formVals.current)); dispatch({ type: "set_field", name, value, error: validate?.(value), - }), + }); + }, value: state.form[name], error: state.errors[name], disabled, @@ -149,7 +159,7 @@ const Form = ({ From 34387583797d8321cdd8f04de4781c2b6610217f Mon Sep 17 00:00:00 2001 From: Jeffrin Jojo <135723871+Jeffrin2005@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:22:14 +0530 Subject: [PATCH 2/9] Final-Disable Submit --- src/components/Form/Form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index db867919e99..d37ca8463fa 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -48,7 +48,7 @@ const Form = ({ const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); const [state, dispatch] = useAutoSaveReducer(formReducer, initial); const formVals = useRef(props.defaults); - const [isFormModified, setIsFormModified] = useState(false); // Add this line + const [isFormModified, setIsFormModified] = useState(false); useEffect(() => { if (!asyncGetDefaults) return; From 2557354f821982d568210bb128f07fc669cc5923 Mon Sep 17 00:00:00 2001 From: Jeffrin Jojo <135723871+Jeffrin2005@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:34:34 +0530 Subject: [PATCH 3/9] final form --- src/components/Form/Form.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index d37ca8463fa..1c0ef7946b8 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,4 +1,5 @@ import isEqual from "lodash/isEqual"; +//loadish import { useEffect, useMemo, useRef, useState } from "react"; import { Cancel, Submit } from "@/components/Common/ButtonV2"; @@ -62,13 +63,11 @@ const Form = ({ const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); event.stopPropagation(); - if (validate) { const errors = omitBy(validate(state.form), isEmpty) as FormErrors; - if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); - + // if (errors.$all) { Notification.Error({ msg: errors.$all }); } From 7d7b8f4431824c82df83b9465336c88e5b1602e8 Mon Sep 17 00:00:00 2001 From: Jeffrin Jojo <135723871+Jeffrin2005@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:37:56 +0530 Subject: [PATCH 4/9] final --- src/components/Form/Form.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 1c0ef7946b8..1204419c095 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -49,6 +49,8 @@ const Form = ({ const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); const [state, dispatch] = useAutoSaveReducer(formReducer, initial); const formVals = useRef(props.defaults); + // is_form modify + const [isFormModified, setIsFormModified] = useState(false); useEffect(() => { From b46c85d790e261049a38015733db57f385dfb2d8 Mon Sep 17 00:00:00 2001 From: Jeffrin Jojo <135723871+Jeffrin2005@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:42:29 +0530 Subject: [PATCH 5/9] FIXED-FINAL SUBMIT BUTTON --- src/components/Form/Form.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 1204419c095..8a57f187198 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,5 +1,4 @@ import isEqual from "lodash/isEqual"; -//loadish import { useEffect, useMemo, useRef, useState } from "react"; import { Cancel, Submit } from "@/components/Common/ButtonV2"; @@ -49,7 +48,6 @@ const Form = ({ const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); const [state, dispatch] = useAutoSaveReducer(formReducer, initial); const formVals = useRef(props.defaults); - // is_form modify const [isFormModified, setIsFormModified] = useState(false); From 6a8092fcf073e2c5307aa06b44fa312d2cfc3bee Mon Sep 17 00:00:00 2001 From: Jeffrin Jojo <135723871+Jeffrin2005@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:51:39 +0530 Subject: [PATCH 6/9] orginal-Disable Submit Button --- src/components/Form/Form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 8a57f187198..0b1d36b275f 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -67,7 +67,6 @@ const Form = ({ const errors = omitBy(validate(state.form), isEmpty) as FormErrors; if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); - // if (errors.$all) { Notification.Error({ msg: errors.$all }); } From 96903ce0af72ba9db4838a6455bb2b6433060797 Mon Sep 17 00:00:00 2001 From: Jeffrin Jojo <135723871+Jeffrin2005@users.noreply.github.com> Date: Sun, 22 Dec 2024 23:04:52 +0530 Subject: [PATCH 7/9] updated-removed lodash --- package-lock.json | 145 +++++++++-------------------------- package.json | 8 +- src/components/Form/Form.tsx | 53 +++++++------ 3 files changed, 69 insertions(+), 137 deletions(-) diff --git a/package-lock.json b/package-lock.json index 03f54c64746..55a2bcbd05f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-slot": "^1.1.1", @@ -31,7 +31,6 @@ "@sentry/browser": "^8.45.1", "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.7", - "@types/lodash": "^4.17.13", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", @@ -50,7 +49,6 @@ "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.2", "i18next-http-backend": "^3.0.1", - "lodash": "^4.17.21", "postcss-loader": "^8.1.1", "qrcode.react": "^4.1.0", "raviger": "^4.1.2", @@ -65,7 +63,7 @@ "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", - "xlsx": "^0.18.5" + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "devDependencies": { "@julr/vite-plugin-validate-env": "^1.1.1", @@ -4081,11 +4079,35 @@ } }, "node_modules/@radix-ui/react-label": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", - "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz", + "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==", + "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -6124,11 +6146,6 @@ "optional": true, "peer": true }, - "node_modules/@types/lodash": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", - "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==" - }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -6826,15 +6843,6 @@ "node": ">=0.4.0" } }, - "node_modules/adler-32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", - "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -7697,19 +7705,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/cfb": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", - "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7975,15 +7970,6 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, - "node_modules/codepage": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", - "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -8163,18 +8149,6 @@ } } }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -10333,15 +10307,6 @@ "node": ">= 6" } }, - "node_modules/frac": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -12889,7 +12854,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.castarray": { "version": "4.4.0", @@ -17725,18 +17691,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/ssf": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", - "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", - "license": "Apache-2.0", - "dependencies": { - "frac": "~1.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -20707,24 +20661,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/wmf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", - "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", - "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -21301,19 +21237,10 @@ } }, "node_modules/xlsx": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", - "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "version": "0.20.3", + "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "cfb": "~1.2.1", - "codepage": "~1.15.0", - "crc-32": "~1.2.1", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.3.0" - }, "bin": { "xlsx": "bin/xlsx.njs" }, @@ -21427,4 +21354,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 7440d1d6171..ca0c5262820 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-slot": "^1.1.1", @@ -70,7 +70,6 @@ "@sentry/browser": "^8.45.1", "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.7", - "@types/lodash": "^4.17.13", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", @@ -89,7 +88,6 @@ "i18next": "^23.16.4", "i18next-browser-languagedetector": "^8.0.2", "i18next-http-backend": "^3.0.1", - "lodash": "^4.17.21", "postcss-loader": "^8.1.1", "qrcode.react": "^4.1.0", "raviger": "^4.1.2", @@ -104,7 +102,7 @@ "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", - "xlsx": "^0.18.5" + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "devDependencies": { "@julr/vite-plugin-validate-env": "^1.1.1", @@ -182,4 +180,4 @@ "node": ">=22.11.0" }, "packageManager": "npm@10.9.0" -} +} \ No newline at end of file diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 0b1d36b275f..df35e9a0cf0 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,7 +1,7 @@ -import isEqual from "lodash/isEqual"; import { useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; -import { Cancel, Submit } from "@/components/Common/ButtonV2"; +import ButtonV2 from "@/components/Common/ButtonV2"; import { FieldValidator } from "@/components/Form/FieldValidators"; import { FormContextValue, @@ -44,12 +44,12 @@ const Form = ({ hideCancelButton = false, ...props }: Props) => { + const { t } = useTranslation(); const initial = { form: props.defaults, errors: {} }; const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); const [state, dispatch] = useAutoSaveReducer(formReducer, initial); const formVals = useRef(props.defaults); - - const [isFormModified, setIsFormModified] = useState(false); + const [isModified, setIsModified] = useState(false); useEffect(() => { if (!asyncGetDefaults) return; @@ -58,15 +58,24 @@ const Form = ({ dispatch({ type: "set_form", form }); setIsLoading(false); }); - }, [asyncGetDefaults]); + }, [asyncGetDefaults, dispatch]); + + useEffect(() => { + const hasChanges = + JSON.stringify(state.form) !== JSON.stringify(formVals.current); + setIsModified(hasChanges); + }, [state.form]); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); event.stopPropagation(); + if (validate) { const errors = omitBy(validate(state.form), isEmpty) as FormErrors; + if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); + if (errors.$all) { Notification.Error({ msg: errors.$all }); } @@ -82,14 +91,13 @@ const Form = ({ }); } else if (props.resetFormValsOnSubmit) { dispatch({ type: "set_form", form: formVals.current }); - setIsFormModified(false); } }; const handleCancel = () => { if (props.resetFormValsOnCancel) { dispatch({ type: "set_form", form: formVals.current }); - setIsFormModified(false); + setIsModified(false); } props.onCancel?.(); }; @@ -125,19 +133,13 @@ const Form = ({ return { name, id: name, - onChange: ({ name, value }: FieldChangeEvent) => { - const newForm = { - ...state.form, - [name]: value, - }; - setIsFormModified(!isEqual(newForm, formVals.current)); + onChange: ({ name, value }: FieldChangeEvent) => dispatch({ type: "set_field", name, value, error: validate?.(value), - }); - }, + }), value: state.form[name], error: state.errors[name], disabled, @@ -149,17 +151,22 @@ const Form = ({
{!hideCancelButton && ( - + disabled={disabled} + > + {props.cancelLabel ?? t("common:cancel")} + )} - + disabled={disabled || !isModified} + data-testid="submit-button" + > + {props.submitLabel ?? t("common:submit")} +
From 2c8839228f27e953c865c76e38015a110d0577e8 Mon Sep 17 00:00:00 2001 From: Jeffrin Jojo <135723871+Jeffrin2005@users.noreply.github.com> Date: Tue, 24 Dec 2024 16:37:06 +0530 Subject: [PATCH 8/9] final-Used shadcn form --- package-lock.json | 38 +++++-- package.json | 6 +- src/components/Form/Form.tsx | 204 +++++++++++++++++++---------------- src/components/ui/form.tsx | 180 +++++++++++++++++++++++++++++++ 4 files changed, 326 insertions(+), 102 deletions(-) create mode 100644 src/components/ui/form.tsx diff --git a/package-lock.json b/package-lock.json index 55a2bcbd05f..178b699d7e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@googlemaps/typescript-guards": "^2.0.3", "@headlessui/react": "^2.2.0", "@hello-pangea/dnd": "^17.0.0", + "@hookform/resolvers": "^3.9.1", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-dialog": "^1.1.4", @@ -56,6 +57,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", + "react-hook-form": "^7.54.2", "react-i18next": "^15.1.3", "react-infinite-scroll-component": "^6.1.0", "react-pdf": "^9.1.1", @@ -113,7 +115,7 @@ "vite-plugin-checker": "^0.8.0", "vite-plugin-pwa": "^0.20.5", "vite-plugin-static-copy": "^2.0.0", - "zod": "^3.23.8" + "zod": "^3.24.1" }, "engines": { "node": ">=22.11.0" @@ -2646,6 +2648,14 @@ "react-dom": "^18.0.0" } }, + "node_modules/@hookform/resolvers": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.1.tgz", + "integrity": "sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -4082,7 +4092,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz", "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==", - "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, @@ -4452,7 +4461,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, @@ -16194,6 +16202,21 @@ "react": ">=16.4.1" } }, + "node_modules/react-hook-form": { + "version": "7.54.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz", + "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "15.1.3", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.1.3.tgz", @@ -21309,11 +21332,10 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -21354,4 +21376,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index ca0c5262820..9bba9cd7a73 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@googlemaps/typescript-guards": "^2.0.3", "@headlessui/react": "^2.2.0", "@hello-pangea/dnd": "^17.0.0", + "@hookform/resolvers": "^3.9.1", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", "@radix-ui/react-dialog": "^1.1.4", @@ -95,6 +96,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", + "react-hook-form": "^7.54.2", "react-i18next": "^15.1.3", "react-infinite-scroll-component": "^6.1.0", "react-pdf": "^9.1.1", @@ -152,7 +154,7 @@ "vite-plugin-checker": "^0.8.0", "vite-plugin-pwa": "^0.20.5", "vite-plugin-static-copy": "^2.0.0", - "zod": "^3.23.8" + "zod": "^3.24.1" }, "browserslist": { "production": [ @@ -180,4 +182,4 @@ "node": ">=22.11.0" }, "packageManager": "npm@10.9.0" -} \ No newline at end of file +} diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index df35e9a0cf0..ce4dacf519d 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,7 +1,10 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import ButtonV2 from "@/components/Common/ButtonV2"; +import { Button } from "@/components/ui/button"; +import { Form as FormContainer } from "@/components/ui/form"; + import { FieldValidator } from "@/components/Form/FieldValidators"; import { FormContextValue, @@ -48,129 +51,146 @@ const Form = ({ const initial = { form: props.defaults, errors: {} }; const [isLoading, setIsLoading] = useState(!!asyncGetDefaults); const [state, dispatch] = useAutoSaveReducer(formReducer, initial); + const [isEdited, setIsEdited] = useState(false); const formVals = useRef(props.defaults); - const [isModified, setIsModified] = useState(false); + + interface FormData extends FormDetails { + [key: string]: unknown; + } + + const methods = useForm({ + defaultValues: props.defaults, + mode: "onChange", + }); useEffect(() => { if (!asyncGetDefaults) return; asyncGetDefaults().then((form) => { dispatch({ type: "set_form", form }); + methods.reset(form); setIsLoading(false); }); - }, [asyncGetDefaults, dispatch]); - - useEffect(() => { - const hasChanges = - JSON.stringify(state.form) !== JSON.stringify(formVals.current); - setIsModified(hasChanges); - }, [state.form]); + }, [asyncGetDefaults, methods, dispatch]); - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - event.stopPropagation(); + const handleSubmit = async (data: T) => { + try { + if (validate) { + const errors = omitBy(validate(data), isEmpty) as FormErrors; - if (validate) { - const errors = omitBy(validate(state.form), isEmpty) as FormErrors; + if (Object.keys(errors).length) { + dispatch({ type: "set_errors", errors }); - if (Object.keys(errors).length) { - dispatch({ type: "set_errors", errors }); - - if (errors.$all) { - Notification.Error({ msg: errors.$all }); + if (errors.$all) { + Notification.Error({ msg: errors.$all }); + } + return; } - return; } - } - const errors = await props.onSubmit(state.form); - if (errors) { - dispatch({ - type: "set_errors", - errors: { ...state.errors, ...errors }, - }); - } else if (props.resetFormValsOnSubmit) { - dispatch({ type: "set_form", form: formVals.current }); + const errors = await props.onSubmit(data); + if (errors) { + dispatch({ + type: "set_errors", + errors: { ...state.errors, ...errors }, + }); + } else if (props.resetFormValsOnSubmit) { + dispatch({ type: "set_form", form: formVals.current }); + methods.reset(formVals.current); + setIsEdited(false); + } + } catch (error) { + console.error("Form submission error:", error); + Notification.Error({ msg: t("errors.form.submission") }); } }; const handleCancel = () => { if (props.resetFormValsOnCancel) { dispatch({ type: "set_form", form: formVals.current }); - setIsModified(false); } props.onCancel?.(); + setIsEdited(false); }; const { Provider, Consumer } = useMemo(() => createFormContext(), []); const disabled = isLoading || props.disabled; return ( -
{ - if (e.key === "Enter") { - handleSubmit(e); - } - }} - className={classNames( - "mx-auto w-full", - !props.noPadding && "px-8 py-5 md:px-16 md:py-11", - props.className, - )} - noValidate - > - ) => { - dispatch({ type: "set_state", state: newState }); - props.onDraftRestore?.(newState); + + handleSubmit(data as T))} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + methods.handleSubmit((data) => handleSubmit(data as T))(); + } }} - formData={state.form} - hidden={props.hideRestoreDraft} + className={classNames( + "mx-auto w-full", + !props.noPadding && "px-8 py-5 md:px-16 md:py-11", + props.className, + )} + noValidate > - ) => { - return { - name, - id: name, - onChange: ({ name, value }: FieldChangeEvent) => - dispatch({ - type: "set_field", + + ) => { + dispatch({ type: "set_state", state: newState }); + methods.reset(newState.form); + props.onDraftRestore?.(newState); + setIsEdited(true); + }} + formData={state.form} + hidden={props.hideRestoreDraft} + > + ) => { + return { name, - value, - error: validate?.(value), - }), - value: state.form[name], - error: state.errors[name], - disabled, - }; - }} - > -
- {props.children} -
-
- {!hideCancelButton && ( - - {props.cancelLabel ?? t("common:cancel")} - - )} - ) => { + dispatch({ + type: "set_field", + name, + value, + error: validate?.(value), + }); + setIsEdited(true); + }, + value: state.form[name], + error: state.errors[name], + disabled, + }; + }} > - {props.submitLabel ?? t("common:submit")} - -
-
-
- +
+ {props.children} +
+
+ {!hideCancelButton && ( + + )} + +
+
+
+ + + ); }; diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx new file mode 100644 index 00000000000..75551e9008b --- /dev/null +++ b/src/components/ui/form.tsx @@ -0,0 +1,180 @@ +"use client"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; +import * as React from "react"; +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form"; + +import { cn } from "@/lib/utils"; + +import { Label } from "@/components/ui/label"; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within "); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue, +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); +}); +FormItem.displayName = "FormItem"; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +
+ + + ); }; diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx deleted file mode 100644 index 75551e9008b..00000000000 --- a/src/components/ui/form.tsx +++ /dev/null @@ -1,180 +0,0 @@ -"use client"; - -import * as LabelPrimitive from "@radix-ui/react-label"; -import { Slot } from "@radix-ui/react-slot"; -import * as React from "react"; -import { - Controller, - ControllerProps, - FieldPath, - FieldValues, - FormProvider, - useFormContext, -} from "react-hook-form"; - -import { cn } from "@/lib/utils"; - -import { Label } from "@/components/ui/label"; - -const Form = FormProvider; - -type FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, -> = { - name: TName; -}; - -const FormFieldContext = React.createContext( - {} as FormFieldContextValue, -); - -const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, ->({ - ...props -}: ControllerProps) => { - return ( - - - - ); -}; - -const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext); - const itemContext = React.useContext(FormItemContext); - const { getFieldState, formState } = useFormContext(); - - const fieldState = getFieldState(fieldContext.name, formState); - - if (!fieldContext) { - throw new Error("useFormField should be used within "); - } - - const { id } = itemContext; - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - }; -}; - -type FormItemContextValue = { - id: string; -}; - -const FormItemContext = React.createContext( - {} as FormItemContextValue, -); - -const FormItem = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => { - const id = React.useId(); - - return ( - -
- - ); -}); -FormItem.displayName = "FormItem"; - -const FormLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => { - const { error, formItemId } = useFormField(); - - return ( -