From 6720c69e6f2d1a4592162360f1e76bf767e51ded Mon Sep 17 00:00:00 2001 From: berkay Date: Thu, 19 Dec 2024 14:35:10 +0300 Subject: [PATCH] form create added --- package-lock.json | 504 +++++++++++++--- package.json | 9 +- .../Building/Build/Build.tsx | 5 - .../ContextComponents/Commons/FormPage.tsx | 168 +++--- .../Commons/FormPageValid.tsx | 186 ++++++ .../ContextComponents/Commons/PageCreate.tsx | 153 +---- .../ContextComponents/Commons/PageUpdate.tsx | 1 - .../retrieveEndpointAndValidations.ts | 8 +- src/components/ui/calendar.tsx | 76 +++ src/components/ui/input.tsx | 14 +- src/components/ui/smart-datetime-input.tsx | 551 ++++++++++++++++++ src/components/zodDecimal.ts | 306 ++++++++++ 12 files changed, 1671 insertions(+), 310 deletions(-) create mode 100644 src/components/ContextComponents/Commons/FormPageValid.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/smart-datetime-input.tsx create mode 100644 src/components/zodDecimal.ts diff --git a/package-lock.json b/package-lock.json index e2514ea..8b846be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,16 @@ "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.2", - "@radix-ui/react-scroll-area": "^1.2.1", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-scroll-area": "^1.2.2", + "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", + "chrono-node": "^2.7.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^3.6.0", "flatpickr": "^4.6.13", "jsvectormap": "^1.6.0", "lucide-react": "^0.462.0", @@ -28,6 +30,7 @@ "next-themes": "^0.4.3", "react": "^18.3.1", "react-apexcharts": "^1.6.0", + "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.2", "react-phone-number-input": "^3.4.9", @@ -685,11 +688,33 @@ "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" }, "node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", + "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", "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-arrow/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==", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -770,6 +795,23 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", @@ -833,6 +875,23 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -951,25 +1010,25 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", - "integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.4.tgz", + "integrity": "sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.6.0" + "react-remove-scroll": "^2.6.1" }, "peerDependencies": { "@types/react": "*", @@ -986,16 +1045,177 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz", + "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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-popover/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz", + "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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-popover/node_modules/@radix-ui/react-portal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", + "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-popover/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-popover/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==", + "dependencies": { + "@radix-ui/react-slot": "1.1.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-popover/node_modules/react-remove-scroll": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", + "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz", + "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-arrow": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", @@ -1017,10 +1237,10 @@ } } }, - "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1031,6 +1251,28 @@ } } }, + "node_modules/@radix-ui/react-popper/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==", + "dependencies": { + "@radix-ui/react-slot": "1.1.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-portal": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", @@ -1099,18 +1341,35 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.1.tgz", - "integrity": "sha512-FnM1fHfCtEZ1JkyfH/1oMiTcFBQvHKl4vD9WnpwkLgtF+UmnXMCad6ECPTaAjcDjam+ndOEJWgHyKDGNteWSHw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz", + "integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==", "dependencies": { "@radix-ui/number": "1.1.0", - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, @@ -1129,13 +1388,91 @@ } } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-scroll-area/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==", + "dependencies": { + "@radix-ui/react-slot": "1.1.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-slot": { + "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==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1629,6 +1966,17 @@ "node": ">= 6" } }, + "node_modules/chrono-node": { + "version": "2.7.7", + "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.7.7.tgz", + "integrity": "sha512-p3S7gotuTPu5oqhRL2p1fLwQXGgdQaRTtWR3e8Di9P1Pa9mzkK5DWR5AWBieMUh2ZdOnPgrK+zCrbbtyuA+D/Q==", + "dependencies": { + "dayjs": "^1.10.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -1755,6 +2103,20 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "devOptional": true }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -1944,14 +2306,6 @@ } } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -2548,6 +2902,19 @@ "react": ">=0.13" } }, + "node_modules/react-day-picker": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -2621,19 +2988,19 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "dependencies": { - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -2642,20 +3009,19 @@ } }, "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "dependencies": { "get-nonce": "^1.0.0", - "invariant": "^2.2.4", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -3107,9 +3473,9 @@ "dev": true }, "node_modules/use-callback-ref": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", - "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "dependencies": { "tslib": "^2.0.0" }, @@ -3117,8 +3483,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { diff --git a/package.json b/package.json index 5d6201a..7a01363 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,16 @@ "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.2", - "@radix-ui/react-scroll-area": "^1.2.1", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-scroll-area": "^1.2.2", + "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", + "chrono-node": "^2.7.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^3.6.0", "flatpickr": "^4.6.13", "jsvectormap": "^1.6.0", "lucide-react": "^0.462.0", @@ -29,6 +31,7 @@ "next-themes": "^0.4.3", "react": "^18.3.1", "react-apexcharts": "^1.6.0", + "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.2", "react-phone-number-input": "^3.4.9", diff --git a/src/components/ContextComponents/Building/Build/Build.tsx b/src/components/ContextComponents/Building/Build/Build.tsx index b606a74..5f675e2 100644 --- a/src/components/ContextComponents/Building/Build/Build.tsx +++ b/src/components/ContextComponents/Building/Build/Build.tsx @@ -88,11 +88,6 @@ const Build: React.FC = () => { ...prev, [key]: component.component, })); - } else { - setEndpointNeedsList((prev) => ({ - ...prev, - [key]: component.notAllowed, - })); } } }) diff --git a/src/components/ContextComponents/Commons/FormPage.tsx b/src/components/ContextComponents/Commons/FormPage.tsx index 4166ac5..e31a9d9 100644 --- a/src/components/ContextComponents/Commons/FormPage.tsx +++ b/src/components/ContextComponents/Commons/FormPage.tsx @@ -14,6 +14,7 @@ import * as z from "zod"; import { Input } from "@/components/ui/input"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; +import { SmartDatetimeInput } from "@/components/ui/smart-datetime-input"; interface FormPageInterface { validatedData: any; @@ -36,20 +37,16 @@ const FormPage: React.FC = ({ ...validatedData, }, }); - - function submitUpdate(formData: FormData) { - let newFormData: any = {}; - Object.entries(Object.fromEntries(formData)).forEach(([key, value]) => { - if (apiValidation[key].fieldType === "integer") { - const newNumber = typeof value === "string" ? 0 : Number(value); - newFormData[key] = newNumber; - } else { - newFormData[key] = value; + function submitUpdate(formData: z.infer) { + let newDataForm: any = {}; + Object.entries(formData).map(([key, value]) => { + if (typeof value === "number" && value !== 0) { + newDataForm[key] = value; + } else if (typeof value === "string" && value !== "") { + newDataForm[key] = value; } }); - const validated = validSchemaZod.safeParse(newFormData); - console.log("validated", validated); - console.log("validated", validated.error); + console.log(newDataForm); } return ( @@ -58,8 +55,7 @@ const FormPage: React.FC = ({
@@ -89,88 +85,96 @@ const FormPage: React.FC = ({ if (apiValidation[key]?.fieldType === "string") { return (
-
+ + + + } - render={({ field }) => ( - - - - - - - )} + name={String(key)} + render={({ field }) => { + return ( + + {apiHeaders[key]} + + + + {String(form.formState.errors[key]?.type) === + "invalid_type" ? ( + + "Lütfen sayısal bir değer giriniz" + + ) : ( + <> + )} + + ); + }} /> - - - - - -
); } else if (apiValidation[key]?.fieldType === "integer") { return (
-
+ + + + } - render={({ field }) => ( - - - - - - - )} + name={String(key)} + render={({ field }) => { + return ( + + {apiHeaders[key]} + + + + {String(form.formState.errors[key]?.type) === + "invalid_type" ? ( + + "Lütfen sayısal bir değer giriniz" + + ) : ( + <> + )} + + ); + }} /> - - - - - -
); diff --git a/src/components/ContextComponents/Commons/FormPageValid.tsx b/src/components/ContextComponents/Commons/FormPageValid.tsx new file mode 100644 index 0000000..115ee12 --- /dev/null +++ b/src/components/ContextComponents/Commons/FormPageValid.tsx @@ -0,0 +1,186 @@ +"use client"; +import React from "react"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; + +import * as z from "zod"; +import { Input } from "@/components/ui/input"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { SmartDatetimeInput } from "@/components/ui/smart-datetime-input"; + +interface FormPageValidInterface { + zodValidation: any; + apiValidation: any; + apiHeaders: any; +} + +const FormPageValid: React.FC = ({ + zodValidation, + apiValidation, + apiHeaders, +}) => { + const validSchemaZod = z.object({ ...zodValidation }); + + const form = useForm>({ + resolver: zodResolver(validSchemaZod), + }); + function submitUpdate(formData: z.infer) { + let newDataForm: any = {}; + Object.entries(formData).map(([key, value]) => { + if (typeof value === "number" && value !== 0) { + newDataForm[key] = value; + } else if (typeof value === "string" && value !== "") { + newDataForm[key] = value; + } + }); + console.log(newDataForm); + } + + return ( + <> + { +
+ + +
+ + + + + + +
+ {Object.keys(zodValidation).map((key: string) => { + if (apiValidation[key]?.fieldType === "string") { + return ( +
+
+ + + + + { + return ( + + {apiHeaders[key]} + + + + {String(form.formState.errors[key]?.type) === + "invalid_type" ? ( + + "Lütfen sayısal bir değer giriniz" + + ) : ( + <> + )} + + ); + }} + /> +
+
+ ); + } else if (apiValidation[key]?.fieldType === "integer") { + return ( +
+
+ + + + + { + return ( + + {apiHeaders[key]} + + + + {String(form.formState.errors[key]?.type) === + "invalid_type" ? ( + + "Lütfen sayısal bir değer giriniz" + + ) : ( + <> + )} + + ); + }} + /> +
+
+ ); + } + })} + + +
+ } + + ); +}; + +export default FormPageValid; diff --git a/src/components/ContextComponents/Commons/PageCreate.tsx b/src/components/ContextComponents/Commons/PageCreate.tsx index 8a61e34..51c81b5 100644 --- a/src/components/ContextComponents/Commons/PageCreate.tsx +++ b/src/components/ContextComponents/Commons/PageCreate.tsx @@ -15,19 +15,22 @@ import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { retrieveValidationsByEndpoint } from "../functions/retrieveEndpointAndValidations"; +import FormPageValid from "./FormPageValid"; interface CreatePageProps { pageInfo: any; endpoint: string; returnToPage: any; + saveFunction: any; } const CreatePage: React.FC = ({ pageInfo, endpoint, returnToPage, + saveFunction, }) => { - const [zodValidation, setZodValidation] = React.useState(z.object({})); + const [zodValidation, setZodValidation] = React.useState(null); const [apiValidation, setApiValidation] = React.useState<{ [key: string]: any; }>({}); @@ -37,18 +40,13 @@ const CreatePage: React.FC = ({ React.useEffect(() => { retrieveValidationsByEndpoint(endpoint).then((validations: any) => { - setZodValidation(validations.zodValidation as any); - setApiHeaders(validations.apiValidation?.headers as Object); - setApiValidation(validations.apiValidation?.validated as Object); + setZodValidation(validations.zodValidation); + setApiValidation(validations.apiValidation?.validated); + setApiHeaders(validations.apiValidation?.headers); }); }, []); - const form = useForm>({ - resolver: zodResolver(zodValidation), - }); - - function closeFormPage() {} - function saveAction() {} + return (
@@ -77,135 +75,14 @@ const CreatePage: React.FC = ({ } /> -
- saveAction()} - label="Kaydet" - bgColor="bg-emerald-700" - icon={ - - - - } - /> -
- { -
- - {Object.keys(apiValidation).map((key: string) => { - const keyValidation = apiValidation[key] || { - fieldType: { type: "string" }, - required: false, - }; - const fieldType = keyValidation.fieldType?.type || "string"; - if (fieldType === "string" && apiValidation[key]) { - return ( -
- -
- } - render={({ field }) => ( - - - - - - - )} - /> - - - - - - -
-
- ); - } else if (fieldType === "integer" && apiValidation[key]) { - return ( -
- -
- } - render={({ field }) => ( - - - - - - - )} - /> - - - - - - -
-
- ); - } - })} -
- - } + {zodValidation && apiValidation && ( + + )}
); }; diff --git a/src/components/ContextComponents/Commons/PageUpdate.tsx b/src/components/ContextComponents/Commons/PageUpdate.tsx index 6c61ef4..dd34eb0 100644 --- a/src/components/ContextComponents/Commons/PageUpdate.tsx +++ b/src/components/ContextComponents/Commons/PageUpdate.tsx @@ -3,7 +3,6 @@ import React from "react"; import EventButton from "./ButtonEvent"; import FormPage from "./FormPage"; -import * as z from "zod"; import { retrieveValidationsByEndpointWithData } from "../functions/retrieveEndpointAndValidations"; interface UpdatePageButtonProps { diff --git a/src/components/ContextComponents/functions/retrieveEndpointAndValidations.ts b/src/components/ContextComponents/functions/retrieveEndpointAndValidations.ts index a421818..331005a 100644 --- a/src/components/ContextComponents/functions/retrieveEndpointAndValidations.ts +++ b/src/components/ContextComponents/functions/retrieveEndpointAndValidations.ts @@ -4,6 +4,7 @@ import { retrieveHeadersAndValidationByEndpoint, retrieveHeadersEndpoint, } from "@/(apicalls)/validations/validations"; +import { ZodDecimal } from "@/components/zodDecimal"; async function retrieveValidationsByEndpoint(endpoint: string) { let apiValidation: any = {}; @@ -97,11 +98,8 @@ async function retrieveValidationsByEndpointWithData( .refine((val) => val !== "" || val !== null); } else if (fieldType === "integer") { zodValidation[key] = required - ? z.number().transform((val) => Number(val)) - : z - .number() - .optional() - .transform((val) => Number(val)); + ? ZodDecimal.create({ coerce: true }) + : ZodDecimal.create({ coerce: true }).optional(); } } }); diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx new file mode 100644 index 0000000..115cff9 --- /dev/null +++ b/src/components/ui/calendar.tsx @@ -0,0 +1,76 @@ +"use client" + +import * as React from "react" +import { ChevronLeft, ChevronRight } from "lucide-react" +import { DayPicker } from "react-day-picker" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +export type CalendarProps = React.ComponentProps + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: CalendarProps) { + return ( + .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" + : "[&:has([aria-selected])]:rounded-md" + ), + day: cn( + buttonVariants({ variant: "ghost" }), + "h-8 w-8 p-0 font-normal aria-selected:opacity-100" + ), + day_range_start: "day-range-start", + day_range_end: "day-range-end", + day_selected: + "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", + day_today: "bg-accent text-accent-foreground", + day_outside: + "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground", + day_disabled: "text-muted-foreground opacity-50", + day_range_middle: + "aria-selected:bg-accent aria-selected:text-accent-foreground", + day_hidden: "invisible", + ...classNames, + }} + components={{ + IconLeft: ({ className, ...props }) => ( + + ), + IconRight: ({ className, ...props }) => ( + + ), + }} + {...props} + /> + ) +} +Calendar.displayName = "Calendar" + +export { Calendar } diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 7530608..69b64fb 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; +import * as React from "react" -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils" const Input = React.forwardRef>( ({ className, type, ...props }, ref) => { @@ -8,15 +8,15 @@ const Input = React.forwardRef>( - ); + ) } -); -Input.displayName = "Input"; +) +Input.displayName = "Input" -export { Input }; +export { Input } diff --git a/src/components/ui/smart-datetime-input.tsx b/src/components/ui/smart-datetime-input.tsx new file mode 100644 index 0000000..3a6bdd4 --- /dev/null +++ b/src/components/ui/smart-datetime-input.tsx @@ -0,0 +1,551 @@ +"use client"; + +import React from "react"; +import { parseDate } from "chrono-node"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ActiveModifiers } from "react-day-picker"; +import { Calendar, CalendarProps } from "@/components/ui/calendar"; +import { Input } from "@/components/ui/input"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { Calendar as CalendarIcon, LucideTextCursorInput } from "lucide-react"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { format } from "date-fns"; + +/* -------------------------------------------------------------------------- */ +/* Inspired By: */ +/* @steventey */ +/* ------------------https://dub.co/blog/smart-datetime-picker--------------- */ +/* -------------------------------------------------------------------------- */ + +/** + * Utility function that parses dates. + * Parses a given date string using the `chrono-node` library. + * + * @param str - A string representation of a date and time. + * @returns A `Date` object representing the parsed date and time, or `null` if the string could not be parsed. + */ +export const parseDateTime = (str: Date | string) => { + if (str instanceof Date) return str; + return parseDate(str); +}; + +/** + * Converts a given timestamp or the current date and time to a string representation in the local time zone. + * format: `HH:mm`, adjusted for the local time zone. + * + * @param timestamp {Date | string} + * @returns A string representation of the timestamp + */ +export const getDateTimeLocal = (timestamp?: Date): string => { + const d = timestamp ? new Date(timestamp) : new Date(); + if (d.toString() === "Invalid Date") return ""; + return new Date(d.getTime() - d.getTimezoneOffset() * 60000) + .toISOString() + .split(":") + .slice(0, 2) + .join(":"); +}; + +/** + * Formats a given date and time object or string into a human-readable string representation. + * "MMM D, YYYY h:mm A" (e.g. "Jan 1, 2023 12:00 PM"). + * + * @param datetime - {Date | string} + * @returns A string representation of the date and time + */ +export const formatDateTime = (datetime: Date | string) => { + return new Date(datetime).toLocaleTimeString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + }); +}; + +const inputBase = + "bg-transparent focus:outline-none focus:ring-0 focus-within:outline-none focus-within:ring-0 sm:text-sm disabled:cursor-not-allowed disabled:opacity-50"; + +// @source: https://www.perplexity.ai/search/in-javascript-how-RfI7fMtITxKr5c.V9Lv5KA#1 +// use this pattern to validate the transformed date string for the natural language input +const naturalInputValidationPattern = + "^[A-Z][a-z]{2}sd{1,2},sd{4},sd{1,2}:d{2}s[AP]M$"; + +const DEFAULT_SIZE = 96; + +/** + * Smart time input Docs: {@link: https://shadcn-extension.vercel.app/docs/smart-time-input} + */ + +interface SmartDatetimeInputProps { + value?: Date; + onValueChange: (date: Date) => void; +} + +interface SmartDatetimeInputContextProps extends SmartDatetimeInputProps { + Time: string; + onTimeChange: (time: string) => void; +} + +const SmartDatetimeInputContext = + React.createContext(null); + +const useSmartDateInput = () => { + const context = React.useContext(SmartDatetimeInputContext); + if (!context) { + throw new Error( + "useSmartDateInput must be used within SmartDateInputProvider", + ); + } + return context; +}; + +export const SmartDatetimeInput = React.forwardRef< + HTMLInputElement, + Omit< + React.InputHTMLAttributes, + "type" | "ref" | "value" | "defaultValue" | "onBlur" + > & + SmartDatetimeInputProps +>(({ className, value, onValueChange, placeholder, disabled }, ref) => { + // ? refactor to be only used with controlled input + /* const [dateTime, setDateTime] = React.useState( + value ?? undefined + ); */ + + const [Time, setTime] = React.useState(""); + + const onTimeChange = React.useCallback((time: string) => { + setTime(time); + }, []); + + return ( + +
+
+ + +
+
+
+ ); +}); + +SmartDatetimeInput.displayName = "DatetimeInput"; + +// Make it a standalone component + +const TimePicker = () => { + const { value, onValueChange, Time, onTimeChange } = useSmartDateInput(); + const [activeIndex, setActiveIndex] = React.useState(-1); + const timestamp = 15; + + const formateSelectedTime = React.useCallback( + (time: string, hour: number, partStamp: number) => { + onTimeChange(time); + + const newVal = parseDateTime(value ?? new Date()); + + if (!newVal) return; + + newVal.setHours( + hour, + partStamp === 0 ? parseInt("00") : timestamp * partStamp, + ); + + // ? refactor needed check if we want to use the new date + + onValueChange(newVal); + }, + [value], + ); + + const handleKeydown = React.useCallback( + (e: React.KeyboardEvent) => { + e.stopPropagation(); + + if (!document) return; + + const moveNext = () => { + const nextIndex = + activeIndex + 1 > DEFAULT_SIZE - 1 ? 0 : activeIndex + 1; + + const currentElm = document.getElementById(`time-${nextIndex}`); + + currentElm?.focus(); + + setActiveIndex(nextIndex); + }; + + const movePrev = () => { + const prevIndex = + activeIndex - 1 < 0 ? DEFAULT_SIZE - 1 : activeIndex - 1; + + const currentElm = document.getElementById(`time-${prevIndex}`); + + currentElm?.focus(); + + setActiveIndex(prevIndex); + }; + + const setElement = () => { + const currentElm = document.getElementById(`time-${activeIndex}`); + + if (!currentElm) return; + + currentElm.focus(); + + const timeValue = currentElm.textContent ?? ""; + + // this should work now haha that hour is what does the trick + + const PM_AM = timeValue.split(" ")[1]; + const PM_AM_hour = parseInt(timeValue.split(" ")[0].split(":")[0]); + const hour = + PM_AM === "AM" + ? PM_AM_hour === 12 + ? 0 + : PM_AM_hour + : PM_AM_hour === 12 + ? 12 + : PM_AM_hour + 12; + + const part = Math.floor( + parseInt(timeValue.split(" ")[0].split(":")[1]) / 15, + ); + + formateSelectedTime(timeValue, hour, part); + }; + + const reset = () => { + const currentElm = document.getElementById(`time-${activeIndex}`); + currentElm?.blur(); + setActiveIndex(-1); + }; + + switch (e.key) { + case "ArrowUp": + movePrev(); + break; + + case "ArrowDown": + moveNext(); + break; + + case "Escape": + reset(); + break; + + case "Enter": + setElement(); + break; + } + }, + [activeIndex, formateSelectedTime], + ); + + const handleClick = React.useCallback( + (hour: number, part: number, PM_AM: string, currentIndex: number) => { + formateSelectedTime( + `${hour}:${part === 0 ? "00" : timestamp * part} ${PM_AM}`, + hour, + part, + ); + setActiveIndex(currentIndex); + }, + [formateSelectedTime], + ); + + const currentTime = React.useMemo(() => { + const timeVal = Time.split(" ")[0]; + return { + hours: parseInt(timeVal.split(":")[0]), + minutes: parseInt(timeVal.split(":")[1]), + }; + }, [Time]); + + React.useEffect(() => { + const getCurrentElementTime = () => { + const timeVal = Time.split(" ")[0]; + const hours = parseInt(timeVal.split(":")[0]); + const minutes = parseInt(timeVal.split(":")[1]); + const PM_AM = Time.split(" ")[1]; + + const formatIndex = + PM_AM === "AM" ? hours : hours === 12 ? hours : hours + 12; + const formattedHours = formatIndex; + + console.log(formatIndex); + + for (let j = 0; j <= 3; j++) { + const diff = Math.abs(j * timestamp - minutes); + const selected = + PM_AM === (formattedHours >= 12 ? "PM" : "AM") && + (minutes <= 53 ? diff < Math.ceil(timestamp / 2) : diff < timestamp); + + if (selected) { + const trueIndex = + activeIndex === -1 ? formattedHours * 4 + j : activeIndex; + + setActiveIndex(trueIndex); + + const currentElm = document.getElementById(`time-${trueIndex}`); + currentElm?.scrollIntoView({ + block: "center", + behavior: "smooth", + }); + } + } + }; + + getCurrentElementTime(); + }, [Time, activeIndex]); + + const height = React.useMemo(() => { + if (!document) return; + const calendarElm = document.getElementById("calendar"); + if (!calendarElm) return; + return calendarElm.style.height; + }, []); + + return ( +
+

Time

+ +
    + {Array.from({ length: 24 }).map((_, i) => { + const PM_AM = i >= 12 ? "PM" : "AM"; + const formatIndex = i > 12 ? i % 12 : i === 0 || i === 12 ? 12 : i; + return Array.from({ length: 4 }).map((_, part) => { + const diff = Math.abs(part * timestamp - currentTime.minutes); + + const trueIndex = i * 4 + part; + + // ? refactor : add the select of the default time on the current device (H:MM) + const isSelected = + (currentTime.hours === i || + currentTime.hours === formatIndex) && + Time.split(" ")[1] === PM_AM && + (currentTime.minutes <= 53 + ? diff < Math.ceil(timestamp / 2) + : diff < timestamp); + + const isSuggested = !value && isSelected; + + const currentValue = `${formatIndex}:${ + part === 0 ? "00" : timestamp * part + } ${PM_AM}`; + + return ( +
  • handleClick(i, part, PM_AM, trueIndex)} + onFocus={() => isSuggested && setActiveIndex(trueIndex)} + > + {currentValue} +
  • + ); + }); + })} +
+
+
+ ); +}; + +const NaturalLanguageInput = React.forwardRef< + HTMLInputElement, + { + placeholder?: string; + disabled?: boolean; + } +>(({ placeholder, ...props }, ref) => { + const { value, onValueChange, Time, onTimeChange } = useSmartDateInput(); + + const _placeholder = placeholder ?? 'e.g. "tomorrow at 5pm" or "in 2 hours"'; + + const [inputValue, setInputValue] = React.useState(""); + + React.useEffect(() => { + const hour = new Date().getHours(); + const timeVal = `${ + hour >= 12 ? hour % 12 : hour + }:${new Date().getMinutes()} ${hour >= 12 ? "PM" : "AM"}`; + setInputValue(value ? formatDateTime(value) : ""); + onTimeChange(value ? Time : timeVal); + }, [value, Time]); + + const handleParse = React.useCallback( + (e: React.ChangeEvent) => { + // parse the date string when the input field loses focus + const parsedDateTime = parseDateTime(e.currentTarget.value); + if (parsedDateTime) { + const PM_AM = parsedDateTime.getHours() >= 12 ? "PM" : "AM"; + //fix the time format for this value + + const PM_AM_hour = parsedDateTime.getHours(); + + const hour = + PM_AM_hour > 12 + ? PM_AM_hour % 12 + : PM_AM_hour === 0 || PM_AM_hour === 12 + ? 12 + : PM_AM_hour; + + onValueChange(parsedDateTime); + setInputValue(formatDateTime(parsedDateTime)); + onTimeChange(`${hour}:${parsedDateTime.getMinutes()} ${PM_AM}`); + } + }, + [value], + ); + + const handleKeydown = React.useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case "Enter": + const parsedDateTime = parseDateTime(e.currentTarget.value); + if (parsedDateTime) { + const PM_AM = parsedDateTime.getHours() >= 12 ? "PM" : "AM"; + //fix the time format for this value + + const PM_AM_hour = parsedDateTime.getHours(); + + const hour = + PM_AM_hour > 12 + ? PM_AM_hour % 12 + : PM_AM_hour === 0 || PM_AM_hour === 12 + ? 12 + : PM_AM_hour; + + onValueChange(parsedDateTime); + setInputValue(formatDateTime(parsedDateTime)); + onTimeChange(`${hour}:${parsedDateTime.getMinutes()} ${PM_AM}`); + } + break; + } + }, + [value], + ); + + return ( + setInputValue(e.currentTarget.value)} + onKeyDown={handleKeydown} + onBlur={handleParse} + className={cn("px-2 mr-0.5 flex-1 border-none h-8 rounded", inputBase)} + {...props} + /> + ); +}); + +NaturalLanguageInput.displayName = "NaturalLanguageInput"; + +type DateTimeLocalInputProps = {} & CalendarProps; + +const DateTimeLocalInput = ({ + className, + ...props +}: DateTimeLocalInputProps) => { + const { value, onValueChange, Time } = useSmartDateInput(); + + const formateSelectedDate = React.useCallback( + ( + date: Date | undefined, + selectedDate: Date, + m: ActiveModifiers, + e: React.MouseEvent, + ) => { + const parsedDateTime = parseDateTime(selectedDate); + + if (parsedDateTime) { + parsedDateTime.setHours( + parseInt(Time.split(":")[0]), + parseInt(Time.split(":")[1]), + ); + onValueChange(parsedDateTime); + } + }, + [value, Time], + ); + + return ( + + + + + +
+ + +
+
+
+ ); +}; + +DateTimeLocalInput.displayName = "DateTimeLocalInput"; diff --git a/src/components/zodDecimal.ts b/src/components/zodDecimal.ts new file mode 100644 index 0000000..ae0236f --- /dev/null +++ b/src/components/zodDecimal.ts @@ -0,0 +1,306 @@ +import { + INVALID, + ParseContext, + ParseInput, + ParseReturnType, + ParseStatus, + RawCreateParams, + ZodIssueCode, + ZodParsedType, + ZodType, + ZodTypeDef, + addIssueToContext, +} from "zod"; + +export type ZodDecimalCheck = + | { kind: "precision"; value: number; message?: string } + | { kind: "wholeNumber"; value: number; message?: string } + | { kind: "min"; value: number; inclusive: boolean; message?: string } + | { kind: "max"; value: number; inclusive: boolean; message?: string } + | { kind: "finite"; message?: string }; + +const zodDecimalKind = "ZodDecimal"; + +export interface ZodDecimalDef extends ZodTypeDef { + checks: ZodDecimalCheck[]; + typeName: typeof zodDecimalKind; + coerce: boolean; +} + +const precisionRegex = /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class ZodDecimal extends ZodType { + // eslint-disable-next-line @typescript-eslint/naming-convention + _parse(input: ParseInput): ParseReturnType { + // detect decimal js object + if ( + input.data !== null && + typeof input.data === "object" && + "toNumber" in input.data + ) { + input.data = input.data.toNumber(); + } + if (this._def.coerce) { + input.data = Number(input.data); + } + + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.number) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.number, + received: ctx.parsedType, + }); + return INVALID; + } + + let ctx: undefined | ParseContext = undefined; + const status = new ParseStatus(); + + for (const check of this._def.checks) { + if (check.kind === "precision") { + const parts = input.data.toString().match(precisionRegex); + const decimals = Math.max( + (parts[1] ? parts[1].length : 0) - + (parts[2] ? parseInt(parts[2], 10) : 0), + 0 + ); + if (decimals > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.custom, + message: check.message, + params: { + precision: check.value, + }, + }); + status.dirty(); + } + } else if (check.kind === "wholeNumber") { + const wholeNumber = input.data.toString().split(".")[0]; + const tooLong = wholeNumber.length > check.value; + + if (tooLong) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.custom, + message: check.message, + params: { + wholeNumber: check.value, + }, + }); + status.dirty(); + } + } else if (check.kind === "min") { + const tooSmall = check.inclusive + ? input.data < check.value + : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "number", + inclusive: check.inclusive, + exact: false, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "max") { + const tooBig = check.inclusive + ? input.data > check.value + : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "number", + inclusive: check.inclusive, + exact: false, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "finite") { + if (!Number.isFinite(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_finite, + message: check.message, + }); + status.dirty(); + } + } + } + + return { status: status.value, value: input.data }; + } + + static create = ( + params?: RawCreateParams & { coerce?: true } + ): ZodDecimal => { + return new ZodDecimal({ + checks: [], + typeName: zodDecimalKind, + coerce: params?.coerce ?? false, + }); + }; + + protected setLimit( + kind: "min" | "max", + value: number, + inclusive: boolean, + message?: string + ): ZodDecimal { + return new ZodDecimal({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message, + }, + ], + }); + } + + _addCheck(check: ZodDecimalCheck): ZodDecimal { + return new ZodDecimal({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + + lte(value: number, message?: string): ZodDecimal { + return this.setLimit("max", value, true, message); + } + + lt(value: number, message?: string): ZodDecimal { + return this.setLimit("max", value, false, message); + } + max = this.lte; + + gt(value: number, message?: string): ZodDecimal { + return this.setLimit("min", value, false, message); + } + gte(value: number, message?: string): ZodDecimal { + return this.setLimit("min", value, true, message); + } + + min = this.gte; + + precision(value: number, message?: string): ZodDecimal { + return this._addCheck({ + kind: "precision", + value, + message, + }); + } + wholeNumber(value: number, message?: string): ZodDecimal { + return this._addCheck({ + kind: "wholeNumber", + value, + message, + }); + } + + get minValue() { + let min: number | null = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) min = ch.value; + } + } + return min; + } + + get maxValue() { + let max: number | null = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) max = ch.value; + } + } + return max; + } + + positive(message?: string) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: false, + message, + }); + } + + negative(message?: string) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: false, + message, + }); + } + + nonpositive(message?: string) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: true, + message, + }); + } + + nonnegative(message?: string) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: true, + message, + }); + } + + finite(message?: string) { + return this._addCheck({ + kind: "finite", + message, + }); + } + + safe(message?: string) { + return this._addCheck({ + kind: "min", + inclusive: true, + value: Number.MIN_SAFE_INTEGER, + message, + })._addCheck({ + kind: "max", + inclusive: true, + value: Number.MAX_SAFE_INTEGER, + message, + }); + } + + get isFinite() { + let max: number | null = null, + min: number | null = null; + for (const ch of this._def.checks) { + if (ch.kind === "finite") { + return true; + } else if (ch.kind === "min") { + if (min === null || ch.value > min) min = ch.value; + } else if (ch.kind === "max") { + if (max === null || ch.value < max) max = ch.value; + } + } + return Number.isFinite(min) && Number.isFinite(max); + } +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const zodDecimal = ZodDecimal.create;