diff --git a/package.json b/package.json index 496c18de..61b256ca 100644 --- a/package.json +++ b/package.json @@ -34,16 +34,18 @@ "@emotion/styled": "^11.10.6", "@mui/icons-material": "^5.11.11", "@mui/material": "^5.11.12", + "@mui/x-date-pickers": "^7.7.1", "@reduxjs/toolkit": "^2.0.1", + "dayjs": "^1.11.11", "formik": "^2.2.9", "js-cookie": "^3.0.5", "qs": "^6.11.2", - "yup": "^1.1.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^9.1.0", "react-router-dom": "^6.23.1", - "serve": "^14.2.3" + "serve": "^14.2.3", + "yup": "^1.1.1" }, "//": [ "`peerDependencies` should contain everything required to build and test a", @@ -55,8 +57,8 @@ "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "@types/js-cookie": "^3.0.3", - "@types/qs": "^6.9.7", "@types/node": "^20.14.2", + "@types/qs": "^6.9.7", "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.2.1", @@ -78,8 +80,8 @@ "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "@types/js-cookie": "^3.0.3", - "@types/qs": "^6.9.7", "@types/node": "^20.14.2", + "@types/qs": "^6.9.7", "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.2.1", diff --git a/src/components/form/DatePickerField.tsx b/src/components/form/DatePickerField.tsx new file mode 100644 index 00000000..3c79bc21 --- /dev/null +++ b/src/components/form/DatePickerField.tsx @@ -0,0 +1,99 @@ +import { + DatePicker, + LocalizationProvider, + type DatePickerProps, + type PickerValidDate, +} from "@mui/x-date-pickers" +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs" +import dayjs, { type Dayjs } from "dayjs" +import "dayjs/locale/en-gb" +import { Field, type FieldConfig, type FieldProps } from "formik" +import * as yup from "yup" + +import { schemaToFieldValidator } from "../../utils/form" + +export interface DatePickerFieldProps< + TDate extends PickerValidDate, + TEnableAccessibleFieldDOMStructure extends boolean = false, +> extends Omit< + DatePickerProps, + "name" | "value" | "onChange" | "slotProps" + > { + name: string + required?: boolean + min?: string | Date + max?: string | Date +} + +const DatePickerField = < + TDate extends PickerValidDate, + TEnableAccessibleFieldDOMStructure extends boolean = false, +>({ + name, + required, + min, + max, + ...otherDatePickerProps +}: DatePickerFieldProps< + TDate, + TEnableAccessibleFieldDOMStructure +>): JSX.Element => { + let schema = yup.date() + if (required) schema = schema.required() + if (min) schema = schema.min(min) + if (max) schema = schema.max(max) + + const fieldConfig: FieldConfig = { + name, + type: "date", + validate: schemaToFieldValidator(schema), + } + + return ( + + {({ form }: FieldProps) => { + let value = form.values[name] + value = value ? dayjs(value) : null + + function handleChange(value: Dayjs | null) { + form.setFieldValue( + name, + value && value.isValid() ? value.format("YYYY-MM-DD") : null, + true, + ) + } + + return ( + + { + // @ts-expect-error + handleChange(value as Dayjs | null) + }, + onBlur: form.handleBlur, + required, + error: form.touched[name] && Boolean(form.errors[name]), + helperText: (form.touched[name] && form.errors[name]) as + | false + | string, + }, + }} + {...otherDatePickerProps} + /> + + ) + }} + + ) +} + +export default DatePickerField diff --git a/yarn.lock b/yarn.lock index d8ca6603..58200dac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1117,6 +1117,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9" @@ -1495,7 +1502,7 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@mui/base@5.0.0-beta.40": +"@mui/base@5.0.0-beta.40", "@mui/base@^5.0.0-beta.40": version "5.0.0-beta.40" resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== @@ -1547,6 +1554,15 @@ "@mui/utils" "^5.15.14" prop-types "^15.8.1" +"@mui/private-theming@^5.15.20": + version "5.15.20" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.20.tgz#028c4e3c717a13691ac2c8c98e29aa819d89001a" + integrity sha512-BK8F94AIqSrnaPYXf2KAOjGZJgWfvqAVQ2gVR3EryvQFtuBnG6RwodxrCvd3B48VuMy6Wsk897+lQMUxJyk+6g== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.15.20" + prop-types "^15.8.1" + "@mui/styled-engine@^5.15.14": version "5.15.14" resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" @@ -1571,6 +1587,20 @@ csstype "^3.1.3" prop-types "^15.8.1" +"@mui/system@^5.15.20": + version "5.15.20" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.20.tgz#f1933aabc4c10f8580c7a951ca3b88542ef0f76b" + integrity sha512-LoMq4IlAAhxzL2VNUDBTQxAb4chnBe8JvRINVNDiMtHE2PiPOoHlhOPutSxEbaL5mkECPVWSv6p8JEV+uykwIA== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.15.20" + "@mui/styled-engine" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.20" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + "@mui/types@^7.2.14": version "7.2.14" resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" @@ -1586,6 +1616,30 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/utils@^5.15.20": + version "5.15.20" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.20.tgz#92778d749ce5ded1598639b4e684aaedb1146e08" + integrity sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A== + dependencies: + "@babel/runtime" "^7.23.9" + "@types/prop-types" "^15.7.11" + prop-types "^15.8.1" + react-is "^18.2.0" + +"@mui/x-date-pickers@^7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-7.7.1.tgz#34c0cef5936b75d395a74faf34355cda0a986613" + integrity sha512-p7/TY8QcdQd6RelNqzW5q89GeUFctvZnDHTfQVEC0l0nAy7ArE6u21uNF8QWGrijZoJXCM+OlIRzlZADaUPpWA== + dependencies: + "@babel/runtime" "^7.24.7" + "@mui/base" "^5.0.0-beta.40" + "@mui/system" "^5.15.20" + "@mui/utils" "^5.15.20" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -2561,7 +2615,7 @@ clipboardy@3.0.0: execa "^5.1.1" is-wsl "^2.2.0" -clsx@^2.1.0: +clsx@^2.1.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -2739,6 +2793,11 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +dayjs@^1.11.11: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"