form create added
This commit is contained in:
parent
44c458cd4c
commit
6720c69e6f
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -88,11 +88,6 @@ const Build: React.FC = () => {
|
|||
...prev,
|
||||
[key]: component.component,
|
||||
}));
|
||||
} else {
|
||||
setEndpointNeedsList((prev) => ({
|
||||
...prev,
|
||||
[key]: component.notAllowed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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<FormPageInterface> = ({
|
|||
...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<typeof validSchemaZod>) {
|
||||
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<FormPageInterface> = ({
|
|||
<div>
|
||||
<Form {...form}>
|
||||
<form
|
||||
action={submitUpdate}
|
||||
// onSubmit={form.handleSubmit(submitUpdate)}
|
||||
onSubmit={form.handleSubmit(submitUpdate)}
|
||||
className="space-y-5 max-w-3xl mx-auto py-10"
|
||||
>
|
||||
<div className=" absolute w-[80px] right-20 bg-emerald-700">
|
||||
|
|
@ -89,88 +85,96 @@ const FormPage: React.FC<FormPageInterface> = ({
|
|||
if (apiValidation[key]?.fieldType === "string") {
|
||||
return (
|
||||
<div className="mb-4" key={`${key}-header`}>
|
||||
<label className="mb-2.5 block font-medium text-black dark:text-white">
|
||||
{apiHeaders[key]}
|
||||
</label>
|
||||
<div className="relative" key={key}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="absolute right-4 top-1/2 lucide lucide-pencil"
|
||||
>
|
||||
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={key as keyof z.infer<typeof zodValidation>}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 text-black outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:text-white dark:focus:border-primary"
|
||||
{...field}
|
||||
value={field.value}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
name={String(key)}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>{apiHeaders[key]}</FormLabel>
|
||||
<FormControl>
|
||||
<SmartDatetimeInput
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
placeholder="e.g. Tomorrow morning 9am"
|
||||
/>
|
||||
</FormControl>
|
||||
{String(form.formState.errors[key]?.type) ===
|
||||
"invalid_type" ? (
|
||||
<span id={key} className="text-red-700">
|
||||
"Lütfen sayısal bir değer giriniz"
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<span className="absolute right-4 top-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-pencil"
|
||||
>
|
||||
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (apiValidation[key]?.fieldType === "integer") {
|
||||
return (
|
||||
<div className="mb-4" key={`${key}-header`}>
|
||||
<label className="mb-2.5 block font-medium text-black dark:text-white">
|
||||
{apiHeaders[key]}
|
||||
</label>
|
||||
<div className="relative" key={key}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="absolute right-4 top-1/2 lucide lucide-pencil"
|
||||
>
|
||||
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={key as keyof z.infer<typeof zodValidation>}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<input
|
||||
type="number"
|
||||
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 text-black outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:text-white dark:focus:border-primary"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
name={String(key)}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>{apiHeaders[key]}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 text-black outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:text-white dark:focus:border-primary"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
/>
|
||||
</FormControl>
|
||||
{String(form.formState.errors[key]?.type) ===
|
||||
"invalid_type" ? (
|
||||
<span id={key} className="text-red-700">
|
||||
"Lütfen sayısal bir değer giriniz"
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<span className="absolute right-4 top-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-pencil"
|
||||
>
|
||||
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<FormPageValidInterface> = ({
|
||||
zodValidation,
|
||||
apiValidation,
|
||||
apiHeaders,
|
||||
}) => {
|
||||
const validSchemaZod = z.object({ ...zodValidation });
|
||||
|
||||
const form = useForm<z.infer<typeof validSchemaZod>>({
|
||||
resolver: zodResolver(validSchemaZod),
|
||||
});
|
||||
function submitUpdate(formData: z.infer<typeof validSchemaZod>) {
|
||||
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 (
|
||||
<>
|
||||
{
|
||||
<div>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(submitUpdate)}
|
||||
className="space-y-5 max-w-3xl mx-auto py-10"
|
||||
>
|
||||
<div className=" absolute w-[80px] right-20 bg-emerald-700">
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-check"
|
||||
>
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
type="submit"
|
||||
value="Kaydet"
|
||||
className="w-full cursor-pointer rounded-lg border p-4 text-white transition hover:bg-opacity-90"
|
||||
/>
|
||||
</div>
|
||||
{Object.keys(zodValidation).map((key: string) => {
|
||||
if (apiValidation[key]?.fieldType === "string") {
|
||||
return (
|
||||
<div className="mb-4" key={`${key}-header`}>
|
||||
<div className="relative" key={key}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="absolute right-4 top-1/2 lucide lucide-pencil"
|
||||
>
|
||||
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={String(key)}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>{apiHeaders[key]}</FormLabel>
|
||||
<FormControl>
|
||||
<SmartDatetimeInput
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
placeholder="e.g. Tomorrow morning 9am"
|
||||
/>
|
||||
</FormControl>
|
||||
{String(form.formState.errors[key]?.type) ===
|
||||
"invalid_type" ? (
|
||||
<span id={key} className="text-red-700">
|
||||
"Lütfen sayısal bir değer giriniz"
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (apiValidation[key]?.fieldType === "integer") {
|
||||
return (
|
||||
<div className="mb-4" key={`${key}-header`}>
|
||||
<div className="relative" key={key}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="absolute right-4 top-1/2 lucide lucide-pencil"
|
||||
>
|
||||
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={String(key)}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>{apiHeaders[key]}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 text-black outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:text-white dark:focus:border-primary"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
/>
|
||||
</FormControl>
|
||||
{String(form.formState.errors[key]?.type) ===
|
||||
"invalid_type" ? (
|
||||
<span id={key} className="text-red-700">
|
||||
"Lütfen sayısal bir değer giriniz"
|
||||
</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormPageValid;
|
||||
|
|
@ -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<CreatePageProps> = ({
|
||||
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<CreatePageProps> = ({
|
|||
|
||||
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<z.infer<typeof zodValidation>>({
|
||||
resolver: zodResolver(zodValidation),
|
||||
});
|
||||
|
||||
function closeFormPage() {}
|
||||
function saveAction() {}
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -77,135 +75,14 @@ const CreatePage: React.FC<CreatePageProps> = ({
|
|||
</svg>
|
||||
}
|
||||
/>
|
||||
<div className="absolute right-0">
|
||||
<EventButton
|
||||
onClick={() => saveAction()}
|
||||
label="Kaydet"
|
||||
bgColor="bg-emerald-700"
|
||||
icon={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-check"
|
||||
>
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit()}
|
||||
className="space-y-5 max-w-3xl mx-auto py-10"
|
||||
>
|
||||
{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 (
|
||||
<div className="mb-4" key={`${key}-header`}>
|
||||
<label className="mb-2.5 block font-medium text-black dark:text-white">
|
||||
{apiHeaders[key]}
|
||||
</label>
|
||||
<div className="relative" key={key}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={key as keyof z.infer<typeof zodValidation>}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 text-black outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:text-white dark:focus:border-primary"
|
||||
{...field}
|
||||
value={field.value}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<span className="absolute right-4 top-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-pencil"
|
||||
>
|
||||
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (fieldType === "integer" && apiValidation[key]) {
|
||||
return (
|
||||
<div className="mb-4" key={`${key}-header`}>
|
||||
<label className="mb-2.5 block font-medium text-black dark:text-white">
|
||||
{apiHeaders[key]}
|
||||
</label>
|
||||
<div className="relative" key={key}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={key as keyof z.infer<typeof zodValidation>}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 text-black outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:text-white dark:focus:border-primary"
|
||||
{...field}
|
||||
value={field.value}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<span className="absolute right-4 top-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-pencil"
|
||||
>
|
||||
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
||||
<path d="m15 5 4 4" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</form>
|
||||
</Form>
|
||||
}
|
||||
{zodValidation && apiValidation && (
|
||||
<FormPageValid
|
||||
zodValidation={zodValidation}
|
||||
apiValidation={apiValidation}
|
||||
apiHeaders={apiHeaders}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<typeof DayPicker>
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
month: "space-y-4",
|
||||
caption: "flex justify-center pt-1 relative items-center",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "space-x-1 flex items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-y-1",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: cn(
|
||||
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
||||
props.mode === "range"
|
||||
? "[&:has(>.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 }) => (
|
||||
<ChevronLeft className={cn("h-4 w-4", className)} {...props} />
|
||||
),
|
||||
IconRight: ({ className, ...props }) => (
|
||||
<ChevronRight className={cn("h-4 w-4", className)} {...props} />
|
||||
),
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Calendar.displayName = "Calendar"
|
||||
|
||||
export { Calendar }
|
||||
|
|
@ -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<HTMLInputElement, React.ComponentProps<"input">>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
|
|
@ -8,15 +8,15 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-14 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input };
|
||||
export { Input }
|
||||
|
|
|
|||
|
|
@ -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<SmartDatetimeInputContextProps | null>(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<HTMLInputElement>,
|
||||
"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<Date | undefined>(
|
||||
value ?? undefined
|
||||
); */
|
||||
|
||||
const [Time, setTime] = React.useState<string>("");
|
||||
|
||||
const onTimeChange = React.useCallback((time: string) => {
|
||||
setTime(time);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SmartDatetimeInputContext.Provider
|
||||
value={{ value, onValueChange, Time, onTimeChange }}
|
||||
>
|
||||
<div className="flex items-center justify-center">
|
||||
<div
|
||||
className={cn(
|
||||
"flex gap-1 w-full p-1 items-center justify-between rounded-md border transition-all",
|
||||
"focus-within:outline-0 focus:outline-0 focus:ring-0",
|
||||
"placeholder:text-muted-foreground focus-visible:outline-0 ",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<DateTimeLocalInput />
|
||||
<NaturalLanguageInput
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SmartDatetimeInputContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
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<HTMLDivElement>) => {
|
||||
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 (
|
||||
<div className="space-y-2 pr-3 py-3 relative ">
|
||||
<h3 className="text-sm font-medium ">Time</h3>
|
||||
<ScrollArea
|
||||
onKeyDown={handleKeydown}
|
||||
className="h-[90%] w-full focus-visible:outline-0 focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:border-0 py-0.5"
|
||||
style={{
|
||||
height,
|
||||
}}
|
||||
>
|
||||
<ul
|
||||
className={cn(
|
||||
"flex items-center flex-col gap-1 h-full max-h-56 w-28 px-1 py-0.5",
|
||||
)}
|
||||
>
|
||||
{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 (
|
||||
<li
|
||||
tabIndex={isSelected ? 0 : -1}
|
||||
id={`time-${trueIndex}`}
|
||||
key={`time-${trueIndex}`}
|
||||
aria-label="currentTime"
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: isSuggested
|
||||
? "secondary"
|
||||
: isSelected
|
||||
? "default"
|
||||
: "outline",
|
||||
}),
|
||||
"h-8 px-3 w-full text-sm focus-visible:outline-0 outline-0 focus-visible:border-0 cursor-default ring-0",
|
||||
)}
|
||||
onClick={() => handleClick(i, part, PM_AM, trueIndex)}
|
||||
onFocus={() => isSuggested && setActiveIndex(trueIndex)}
|
||||
>
|
||||
{currentValue}
|
||||
</li>
|
||||
);
|
||||
});
|
||||
})}
|
||||
</ul>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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<string>("");
|
||||
|
||||
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<HTMLInputElement>) => {
|
||||
// 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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<Input
|
||||
ref={ref}
|
||||
type="text"
|
||||
placeholder={_placeholder}
|
||||
value={inputValue}
|
||||
onChange={(e) => 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 (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size={"icon"}
|
||||
className={cn(
|
||||
"size-9 flex items-center justify-center font-normal",
|
||||
!value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="size-4" />
|
||||
<span className="sr-only">calender</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" sideOffset={8}>
|
||||
<div className="flex gap-1">
|
||||
<Calendar
|
||||
{...props}
|
||||
id={"calendar"}
|
||||
className={cn("peer flex justify-end", inputBase, className)}
|
||||
mode="single"
|
||||
selected={value}
|
||||
onSelect={formateSelectedDate}
|
||||
initialFocus
|
||||
/>
|
||||
<TimePicker />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
DateTimeLocalInput.displayName = "DateTimeLocalInput";
|
||||
|
|
@ -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<number, ZodDecimalDef, any> {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
_parse(input: ParseInput): ParseReturnType<number> {
|
||||
// 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;
|
||||
Loading…
Reference in New Issue