feat: add NumberInput component for enhanced numeric input handling; integrate react-number-format

This commit is contained in:
Marco Pedone 2025-11-11 18:43:51 +01:00
parent 24253f0cfb
commit 7666f08307
5 changed files with 366 additions and 41 deletions

View file

@ -74,6 +74,7 @@
"react-hot-toast": "^2.5.2", "react-hot-toast": "^2.5.2",
"react-is": "^19.1.1", "react-is": "^19.1.1",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"react-number-format": "^5.4.4",
"react-pdf": "^10.1.0", "react-pdf": "^10.1.0",
"react-phone-number-input": "^3.4.12", "react-phone-number-input": "^3.4.12",
"react-reverse-portal": "^2.3.0", "react-reverse-portal": "^2.3.0",
@ -831,6 +832,7 @@
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2", "@babel/code-frame": "^7.26.2",
@ -1233,6 +1235,7 @@
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
@ -1369,6 +1372,7 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
@ -1889,6 +1893,7 @@
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz",
"integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==", "integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@floating-ui/core": "^1.7.3", "@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10" "@floating-ui/utils": "^0.2.10"
@ -6290,6 +6295,7 @@
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
@ -6300,6 +6306,7 @@
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
@ -6492,6 +6499,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.6", "nanoid": "^3.3.6",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
@ -6539,6 +6547,7 @@
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -6549,6 +6558,7 @@
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.25.0" "scheduler": "^0.25.0"
}, },
@ -7935,6 +7945,7 @@
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.8.0.tgz", "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.8.0.tgz",
"integrity": "sha512-DNXRfYUgkZlrniQORbA/wH8CdFRhiBSE0R56gYU0V5vvpJ9WZwvGrz9tBAZmfq2aTgw6SK7mNpmTizGzLWVezw==", "integrity": "sha512-DNXRfYUgkZlrniQORbA/wH8CdFRhiBSE0R56gYU0V5vvpJ9WZwvGrz9tBAZmfq2aTgw6SK7mNpmTizGzLWVezw==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12.16" "node": ">=12.16"
} }
@ -8360,6 +8371,7 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
"integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==", "integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@tanstack/query-core": "5.85.5" "@tanstack/query-core": "5.85.5"
}, },
@ -8436,6 +8448,7 @@
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.2.0.tgz", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.2.0.tgz",
"integrity": "sha512-1Dk1AIwzJejcyi4FOEcKoBSLMkHMfxeDiQ0daG51eS1IZ1ZSF+Xlhg9OD5sAjbUTGQjK1HfU6kGwS9wJcR/9ZQ==", "integrity": "sha512-1Dk1AIwzJejcyi4FOEcKoBSLMkHMfxeDiQ0daG51eS1IZ1ZSF+Xlhg9OD5sAjbUTGQjK1HfU6kGwS9wJcR/9ZQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/ueberdosis" "url": "https://github.com/sponsors/ueberdosis"
@ -8658,6 +8671,7 @@
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.2.0.tgz", "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.2.0.tgz",
"integrity": "sha512-/3ZL9MpgDnEOv8tPwFwW3ct/TBbxZg3r3rN9noLHOR+YgiZKwbpMt9pDSJGu8y++rzT3vIHVmRRp0xFOXBvptQ==", "integrity": "sha512-/3ZL9MpgDnEOv8tPwFwW3ct/TBbxZg3r3rN9noLHOR+YgiZKwbpMt9pDSJGu8y++rzT3vIHVmRRp0xFOXBvptQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/ueberdosis" "url": "https://github.com/sponsors/ueberdosis"
@ -8763,6 +8777,7 @@
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.2.0.tgz", "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.2.0.tgz",
"integrity": "sha512-voEvFhStH1fxw6PF5Hb7kj7PH+BMYM3d0S6d/V0bNdGB7mMsILruXkXIsxvP8Dkskv07Xmfs+iU6pdVy0hWD7Q==", "integrity": "sha512-voEvFhStH1fxw6PF5Hb7kj7PH+BMYM3d0S6d/V0bNdGB7mMsILruXkXIsxvP8Dkskv07Xmfs+iU6pdVy0hWD7Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/ueberdosis" "url": "https://github.com/sponsors/ueberdosis"
@ -8777,6 +8792,7 @@
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.2.0.tgz", "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.2.0.tgz",
"integrity": "sha512-2UTlVts1Q7snQaUlxUtXfwEndNgcBcEIf74f1aEzcLzJRwBRRexCS6M0n+F50IifoB0OVBZENMBv1X/YXIPGPA==", "integrity": "sha512-2UTlVts1Q7snQaUlxUtXfwEndNgcBcEIf74f1aEzcLzJRwBRRexCS6M0n+F50IifoB0OVBZENMBv1X/YXIPGPA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"prosemirror-changeset": "^2.3.0", "prosemirror-changeset": "^2.3.0",
"prosemirror-collab": "^1.3.1", "prosemirror-collab": "^1.3.1",
@ -8878,6 +8894,7 @@
"https://trpc.io/sponsor" "https://trpc.io/sponsor"
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@trpc/server": "11.4.4", "@trpc/server": "11.4.4",
"typescript": ">=5.7.2" "typescript": ">=5.7.2"
@ -8918,6 +8935,7 @@
"https://trpc.io/sponsor" "https://trpc.io/sponsor"
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@tanstack/react-query": "^5.80.3", "@tanstack/react-query": "^5.80.3",
"@trpc/client": "11.4.4", "@trpc/client": "11.4.4",
@ -8935,6 +8953,7 @@
"https://trpc.io/sponsor" "https://trpc.io/sponsor"
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"typescript": ">=5.7.2" "typescript": ">=5.7.2"
} }
@ -9123,6 +9142,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~7.10.0" "undici-types": "~7.10.0"
} }
@ -9191,6 +9211,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz",
"integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
@ -9201,6 +9222,7 @@
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
@ -9449,6 +9471,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -9849,6 +9872,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001735", "caniuse-lite": "^1.0.30001735",
"electron-to-chromium": "^1.5.204", "electron-to-chromium": "^1.5.204",
@ -10456,7 +10480,8 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/d3-array": { "node_modules/d3-array": {
"version": "3.2.4", "version": "3.2.4",
@ -10901,7 +10926,8 @@
"version": "8.6.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/embla-carousel-react": { "node_modules/embla-carousel-react": {
"version": "8.6.0", "version": "8.6.0",
@ -13027,6 +13053,7 @@
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.5.tgz", "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.5.tgz",
"integrity": "sha512-rlB0I/c6FBDWPcQoDtkxi9zIvpmnV5xoIalfCMSMCa7nuA6VGA3F54TW9mEgX4DVf10sXAWCF5fDbamI/5ZpKA==", "integrity": "sha512-rlB0I/c6FBDWPcQoDtkxi9zIvpmnV5xoIalfCMSMCa7nuA6VGA3F54TW9mEgX4DVf10sXAWCF5fDbamI/5ZpKA==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
} }
@ -13053,7 +13080,8 @@
"version": "1.9.4", "version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause" "license": "BSD-2-Clause",
"peer": true
}, },
"node_modules/leaflet-defaulticon-compatibility": { "node_modules/leaflet-defaulticon-compatibility": {
"version": "0.1.2", "version": "0.1.2",
@ -13884,6 +13912,7 @@
"resolved": "https://registry.npmjs.org/next/-/next-15.4.7.tgz", "resolved": "https://registry.npmjs.org/next/-/next-15.4.7.tgz",
"integrity": "sha512-OcqRugwF7n7mC8OSYjvsZhhG1AYSvulor1EIUsIkbbEbf1qoE5EbH36Swj8WhF4cHqmDgkiam3z1c1W0J1Wifg==", "integrity": "sha512-OcqRugwF7n7mC8OSYjvsZhhG1AYSvulor1EIUsIkbbEbf1qoE5EbH36Swj8WhF4cHqmDgkiam3z1c1W0J1Wifg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@next/env": "15.4.7", "@next/env": "15.4.7",
"@swc/helpers": "0.5.15", "@swc/helpers": "0.5.15",
@ -14908,6 +14937,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@ -15238,6 +15268,7 @@
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.3.tgz", "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.3.tgz",
"integrity": "sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==", "integrity": "sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"orderedmap": "^2.0.0" "orderedmap": "^2.0.0"
} }
@ -15267,6 +15298,7 @@
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
"integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"prosemirror-model": "^1.0.0", "prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0", "prosemirror-transform": "^1.0.0",
@ -15315,6 +15347,7 @@
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.1.tgz", "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.1.tgz",
"integrity": "sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==", "integrity": "sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"prosemirror-model": "^1.20.0", "prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0", "prosemirror-state": "^1.0.0",
@ -15604,6 +15637,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -15644,6 +15678,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.26.0" "scheduler": "^0.26.0"
}, },
@ -15939,6 +15974,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
}, },
@ -15987,6 +16023,16 @@
"react-dom": "^19.0.0" "react-dom": "^19.0.0"
} }
}, },
"node_modules/react-number-format": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz",
"integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==",
"license": "MIT",
"peerDependencies": {
"react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-pdf": { "node_modules/react-pdf": {
"version": "10.1.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-10.1.0.tgz", "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-10.1.0.tgz",
@ -16681,6 +16727,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1", "fast-uri": "^3.0.1",
@ -17765,7 +17812,8 @@
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
"integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.2", "version": "2.2.2",
@ -17963,7 +18011,8 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD",
"peer": true
}, },
"node_modules/tunnel-rat": { "node_modules/tunnel-rat": {
"version": "0.1.2", "version": "0.1.2",
@ -18091,6 +18140,7 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -18745,6 +18795,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }

View file

@ -85,6 +85,7 @@
"react-hot-toast": "^2.5.2", "react-hot-toast": "^2.5.2",
"react-is": "^19.1.1", "react-is": "^19.1.1",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"react-number-format": "^5.4.4",
"react-pdf": "^10.1.0", "react-pdf": "^10.1.0",
"react-phone-number-input": "^3.4.12", "react-phone-number-input": "^3.4.12",
"react-reverse-portal": "^2.3.0", "react-reverse-portal": "^2.3.0",

View file

@ -0,0 +1,164 @@
import { ChevronDown, ChevronUp } from "lucide-react";
import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
import { NumericFormat, type NumericFormatProps } from "react-number-format";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
export interface NumberInputProps
extends Omit<NumericFormatProps, "value" | "onValueChange"> {
stepper?: number;
thousandSeparator?: string;
placeholder?: string;
defaultValue?: number;
min?: number;
max?: number;
value?: number; // Controlled value
suffix?: string;
prefix?: string;
onValueChange?: (value: number | undefined) => void;
fixedDecimalScale?: boolean;
decimalScale?: number;
}
export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
(
{
stepper,
thousandSeparator,
placeholder,
defaultValue,
min = -Infinity,
max = Infinity,
onValueChange,
fixedDecimalScale = false,
decimalScale = 0,
suffix,
prefix,
value: controlledValue,
...props
},
externalRef,
) => {
const internalRef = useRef<HTMLInputElement>(null);
const inputRef =
(externalRef as React.RefObject<HTMLInputElement>) || internalRef;
const [value, setValue] = useState<number | undefined>(
controlledValue ?? defaultValue,
);
const handleIncrement = useCallback(() => {
setValue((prev) =>
prev === undefined
? (stepper ?? 1)
: Math.min(prev + (stepper ?? 1), max),
);
}, [stepper, max]);
const handleDecrement = useCallback(() => {
setValue((prev) =>
prev === undefined
? -(stepper ?? 1)
: Math.max(prev - (stepper ?? 1), min),
);
}, [stepper, min]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!inputRef.current) return;
if (document.activeElement === inputRef.current) {
if (e.key === "ArrowUp") {
handleIncrement();
} else if (e.key === "ArrowDown") {
handleDecrement();
}
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [handleIncrement, handleDecrement, inputRef]);
useEffect(() => {
if (controlledValue !== undefined) {
setValue(controlledValue);
}
}, [controlledValue]);
const handleChange = (values: {
value: string;
floatValue: number | undefined;
}) => {
const newValue =
values.floatValue === undefined ? undefined : values.floatValue;
setValue(newValue);
if (onValueChange) {
onValueChange(newValue);
}
};
const handleBlur = () => {
if (value !== undefined) {
if (value < min) {
setValue(min);
if (inputRef.current) {
inputRef.current.value = String(min);
}
} else if (value > max) {
setValue(max);
if (inputRef.current) {
inputRef.current.value = String(min);
}
}
}
};
return (
<div className="flex items-center">
<NumericFormat
allowNegative={min < 0}
className="relative rounded-r-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
customInput={Input}
decimalScale={decimalScale}
fixedDecimalScale={fixedDecimalScale}
getInputRef={inputRef}
max={max}
min={min}
onBlur={handleBlur}
onValueChange={handleChange}
placeholder={placeholder}
prefix={prefix}
suffix={suffix}
thousandSeparator={thousandSeparator}
value={value}
valueIsNumericString
{...props}
/>
<div className="flex flex-col">
<Button
aria-label="Increase value"
className="h-5 rounded-l-none rounded-br-none border-input border-b-[0.5px] border-l-0 px-2 focus-visible:relative"
disabled={value === max}
onClick={handleIncrement}
variant="outline"
>
<ChevronUp size={15} />
</Button>
<Button
aria-label="Decrease value"
className="h-5 rounded-l-none rounded-tr-none border-input border-t-[0.5px] border-l-0 px-2 focus-visible:relative"
disabled={value === min}
onClick={handleDecrement}
variant="outline"
>
<ChevronDown size={15} />
</Button>
</div>
</div>
);
},
);

View file

@ -3,6 +3,7 @@ import { CircleCheck, ClipboardPaste, Pen, Trash2, Wrench } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react"; import { useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useDebounce } from "react-use";
import z from "zod"; import z from "zod";
import { formatCurrency } from "~/lib/utils"; import { formatCurrency } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
@ -21,6 +22,12 @@ import {
CredenzaTitle, CredenzaTitle,
CredenzaTrigger, CredenzaTrigger,
} from "../custom_ui/credenza"; } from "../custom_ui/credenza";
import { NumberInput } from "../custom_ui/InputNumber";
import {
type ListOption,
MultiSelect,
UnpackOptions,
} from "../custom_ui/multiselect";
import { LoadingPage } from "../loading"; import { LoadingPage } from "../loading";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { import {
@ -31,7 +38,6 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "../ui/card"; } from "../ui/card";
import { Label } from "../ui/label"; import { Label } from "../ui/label";
import { Switch } from "../ui/switch"; import { Switch } from "../ui/switch";
import { UploadModal } from "../upload_modal"; import { UploadModal } from "../upload_modal";
@ -373,6 +379,7 @@ const MotivazioneSection = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { data: servizio } = api.servizio.getServizio.useQuery({ servizioId }); const { data: servizio } = api.servizio.getServizio.useQuery({ servizioId });
if (!servizio) return null; if (!servizio) return null;
return ( return (
<Card> <Card>
@ -445,10 +452,126 @@ const FileLink = ({
); );
}; };
const SendConferma = () => { const TransitorioSection = () => {
const { servizio } = useServizio(); const { servizio } = useServizio();
const { data } = useServizioAnnuncio(); const { data } = useServizioAnnuncio();
const { t } = useTranslation(); const { t } = useTranslation();
const { servizioId, userId } = useServizio();
const utils = api.useUtils();
const { mutate: update } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Servizio modificato con successo");
await utils.servizio.getUserServizi.invalidate();
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
},
});
const handlePermanenzaChange = (value: ListOption) => {
update({
servizioId,
data: {
permanenza: value.value !== "" ? parseInt(value.value) : null,
},
});
};
const preferenzeDescrizione = data.desc_it?.match(/Permanenza:.*$/m);
return (
<div className="flex flex-col gap-2">
<div className="max-w-sm">
<Label className="text-base" htmlFor="permanenza">
Permanenza (da preferenze servizio):
</Label>
<MultiSelect
defaultValue={
servizio.permanenza
? {
label: t.preferenze.permanenza[servizio.permanenza] || "",
value: servizio.permanenza.toString(),
}
: undefined
}
elementId="permanenza"
elementName="permanenza"
isMulti={false}
onValueChange={handlePermanenzaChange}
options={UnpackOptions({
options: t.preferenze.permanenza,
})}
placeholder="Seleziona..."
/>
</div>
<p>
<span className="font-semibold">Permanenza in descrizione: </span>
{preferenzeDescrizione
? preferenzeDescrizione[0]
: "errore: testo non trovato in descrizione"}
</p>
</div>
);
};
const StabileSection = () => {
const { servizio, servizioId, userId } = useServizio();
const { data } = useServizioAnnuncio();
const [value, setValue] = useState(servizio.budget);
const utils = api.useUtils();
const { mutate: update } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Servizio modificato con successo");
await utils.servizio.getUserServizi.invalidate();
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
},
});
useDebounce(
() => {
if (value === servizio.budget) return;
update({
servizioId,
data: {
budget: value,
},
});
},
1000,
[value],
);
return (
<div className="flex flex-col gap-2">
<div className="flex max-w-sm flex-col gap-2">
<span className="font-semibold">Budget (da preferenze servizio):</span>
<NumberInput
allowNegative={false}
min={0}
onValueChange={(val) => {
if (val !== undefined) {
setValue(val);
}
}}
stepper={50}
suffix=" €"
value={value}
/>
</div>
<p>
<span className="font-semibold">Prezzo in descrizione: </span>
{formatCurrency(data.prezzo / 100)}
</p>
</div>
);
};
const SendConferma = () => {
const { servizio } = useServizio();
const { data } = useServizioAnnuncio();
const [check, setCheck] = useState(data.hasConfermaAdmin); const [check, setCheck] = useState(data.hasConfermaAdmin);
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate: handleUpdate } = const { mutate: handleUpdate } =
@ -473,40 +596,17 @@ const SendConferma = () => {
controlla i parametri di ricerca prima! controlla i parametri di ricerca prima!
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-10">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<p>Tipologia servizio: {servizio.tipologia}</p>
{(() => {
if (servizio.tipologia === TipologiaPosizioneEnum.Transitorio) {
return (
<>
<p> <p>
Permanenza specificata dall'utente:{" "} <span className="font-semibold">Tipologia servizio: </span>
{servizio.permanenza {servizio.tipologia}
? t.preferenze.permanenza[servizio.permanenza]
: "non inserita"}
</p> </p>
<p> {servizio.tipologia === TipologiaPosizioneEnum.Transitorio ? (
Permanenza in descrizione: {(() => { <TransitorioSection />
const match = data.desc_it?.match(/Permanenza:.*$/m); ) : (
return match <StabileSection />
? `"${match[0]}"` )}
: "errore: testo non trovato in descrizione";
})()}
</p>
</>
);
}
return (
<>
<p>Budget (da preferenze): {formatCurrency(servizio.budget)}</p>
<p>
Canone Mensile (da descrizione):{" "}
{formatCurrency(data.prezzo / 100)}
</p>
</>
);
})()}
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch <Switch
@ -517,7 +617,9 @@ const SendConferma = () => {
setCheck(c); setCheck(c);
}} }}
/> />
<Label htmlFor="conferma">Conferma lavorata</Label> <Label className="text-base" htmlFor="conferma">
Imposta Conferma come Lavorata
</Label>
</div> </div>
</CardContent> </CardContent>
<CardFooter> <CardFooter>

View file

@ -123,6 +123,13 @@ const NewServizioModal = ({
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio modificato con successo"); toast.success("Servizio modificato con successo");
await utils.servizio.getUserServizi.invalidate(); await utils.servizio.getUserServizi.invalidate();
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getServizio.invalidate({
servizioId: editData?.servizio_id,
});
await utils.servizio.getCompatibileAnnunci.invalidate({
servizioId: editData?.servizio_id,
});
setEditData(undefined); setEditData(undefined);
setOpen(false); setOpen(false);
}, },