diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..222861c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/docker-compose.yml b/docker-compose.yml index e3ebf5f..43aef67 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,9 @@ services: - app: - container_name: dashregistratori - build: - context: . + db: + image: postgres restart: unless-stopped + shm_size: 128mb ports: - - 3001:3000 - dns: 1.1.1.1 + - 5432:5432 environment: - NODE_ENV: development + POSTGRES_PASSWORD: postgres diff --git a/package-lock.json b/package-lock.json index cd87683..33ec86e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,24 +8,28 @@ "name": "dash-registratori", "version": "0.1.0", "dependencies": { + "@prisma/client": "^6.18.0", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", "lucide-react": "^0.546.0", "next": "15.5.5", - "prisma": "^6.17.1", "react": "19.1.0", + "react-day-picker": "^9.11.1", "react-dom": "19.1.0", "react-leaflet": "^5.0.0", "tailwind-merge": "^3.3.1" @@ -36,6 +40,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "prisma": "^6.18.0", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "typescript": "^5" @@ -54,6 +59,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "license": "MIT" + }, "node_modules/@emnapi/runtime": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", @@ -727,61 +738,89 @@ "node": ">= 10" } }, + "node_modules/@prisma/client": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", + "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/@prisma/config": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz", - "integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", + "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", - "effect": "3.16.12", + "effect": "3.18.4", "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz", - "integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", + "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz", - "integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", + "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.17.1", - "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", - "@prisma/fetch-engine": "6.17.1", - "@prisma/get-platform": "6.17.1" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/fetch-engine": "6.18.0", + "@prisma/get-platform": "6.18.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz", - "integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==", + "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", + "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz", - "integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", + "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.17.1", - "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", - "@prisma/get-platform": "6.17.1" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz", - "integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", + "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.17.1" + "@prisma/debug": "6.18.0" } }, "node_modules/@radix-ui/primitive": { @@ -926,6 +965,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "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.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -992,6 +1049,24 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "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.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -1184,6 +1259,79 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "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": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "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-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "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-popper": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", @@ -1287,6 +1435,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "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-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -1342,9 +1508,9 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1422,6 +1588,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "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-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -1620,6 +1804,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "devOptional": true, "license": "MIT" }, "node_modules/@swc/helpers": { @@ -1970,6 +2155,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.3", @@ -2018,6 +2204,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -2043,6 +2230,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, "license": "MIT", "dependencies": { "consola": "^3.2.3" @@ -2075,16 +2263,34 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -2097,10 +2303,27 @@ "devOptional": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "license": "MIT" + }, "node_modules/deepmerge-ts": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=16.0.0" @@ -2110,12 +2333,14 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, "license": "MIT" }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, "license": "MIT" }, "node_modules/detect-libc": { @@ -2138,6 +2363,7 @@ "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2147,9 +2373,10 @@ } }, "node_modules/effect": { - "version": "3.16.12", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz", - "integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==", + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -2160,6 +2387,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=14" @@ -2183,12 +2411,14 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "devOptional": true, "license": "MIT" }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, "funding": [ { "type": "individual", @@ -2220,6 +2450,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -2244,6 +2475,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -2644,12 +2876,14 @@ "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, "license": "MIT" }, "node_modules/nypm": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -2669,18 +2903,21 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, "license": "MIT" }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, "license": "MIT" }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, "license": "MIT" }, "node_modules/picocolors": { @@ -2693,6 +2930,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, "license": "MIT", "dependencies": { "confbox": "^0.2.2", @@ -2730,14 +2968,15 @@ } }, "node_modules/prisma": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz", - "integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", + "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.17.1", - "@prisma/engines": "6.17.1" + "@prisma/config": "6.18.0", + "@prisma/engines": "6.18.0" }, "bin": { "prisma": "build/index.js" @@ -2758,6 +2997,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, "funding": [ { "type": "individual", @@ -2774,6 +3014,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, "license": "MIT", "dependencies": { "defu": "^6.1.4", @@ -2789,6 +3030,27 @@ "node": ">=0.10.0" } }, + "node_modules/react-day-picker": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.1.tgz", + "integrity": "sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", @@ -2888,6 +3150,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -3043,6 +3306,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "devOptional": true, "license": "MIT" }, "node_modules/tslib": { diff --git a/package.json b/package.json index c09a790..181f18a 100644 --- a/package.json +++ b/package.json @@ -8,24 +8,28 @@ "start": "next start" }, "dependencies": { + "@prisma/client": "^6.18.0", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", "lucide-react": "^0.546.0", "next": "15.5.5", - "prisma": "^6.17.1", "react": "19.1.0", + "react-day-picker": "^9.11.1", "react-dom": "19.1.0", "react-leaflet": "^5.0.0", "tailwind-merge": "^3.3.1" @@ -36,6 +40,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "prisma": "^6.18.0", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "typescript": "^5" diff --git a/prisma/migrations/20251028145702_migration_0001/migration.sql b/prisma/migrations/20251028145702_migration_0001/migration.sql new file mode 100644 index 0000000..9caa593 --- /dev/null +++ b/prisma/migrations/20251028145702_migration_0001/migration.sql @@ -0,0 +1,41 @@ +-- CreateTable +CREATE TABLE "Intervento" ( + "id" SERIAL NOT NULL, + "id_registratore" INTEGER NOT NULL, + "data" DATE NOT NULL, + "lavoro" TEXT NOT NULL, + "fattura" BOOLEAN NOT NULL, + + CONSTRAINT "Intervento_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Registratore" ( + "id" SERIAL NOT NULL, + "id_cliente" INTEGER NOT NULL, + + CONSTRAINT "Registratore_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Cliente" ( + "id" SERIAL NOT NULL, + "ragione_sociale" VARCHAR(255) NOT NULL, + "email" VARCHAR(255) NOT NULL, + "partita_iva" VARCHAR(255) NOT NULL, + "telefono" VARCHAR(255) NOT NULL, + "sede" VARCHAR(255) NOT NULL, + "sede_url" VARCHAR(255) NOT NULL, + "contratto" VARCHAR(255) NOT NULL, + + CONSTRAINT "Cliente_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Cliente_ragione_sociale_key" ON "Cliente"("ragione_sociale"); + +-- AddForeignKey +ALTER TABLE "Intervento" ADD CONSTRAINT "Intervento_id_registratore_fkey" FOREIGN KEY ("id_registratore") REFERENCES "Registratore"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Registratore" ADD CONSTRAINT "Registratore_id_cliente_fkey" FOREIGN KEY ("id_cliente") REFERENCES "Cliente"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20251104102414_002/migration.sql b/prisma/migrations/20251104102414_002/migration.sql new file mode 100644 index 0000000..d75cfaa --- /dev/null +++ b/prisma/migrations/20251104102414_002/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Registratore" ADD COLUMN "data_acquisto" DATE, +ADD COLUMN "prossima_verifica" DATE, +ADD COLUMN "seriale" VARCHAR(255), +ADD COLUMN "ultima_verifica" DATE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e30eeeb..8bdd8dd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,12 +1,6 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? -// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init - generator client { provider = "prisma-client-js" - output = "../generated/prisma" + output = "../src/generated/prisma" } datasource db { @@ -14,10 +8,24 @@ datasource db { url = env("DATABASE_URL") } +model Intervento { + id Int @id @default(autoincrement()) + id_registratore Int + data DateTime @db.Date + lavoro String @db.Text + fattura Boolean @db.Boolean + registratore Registratore @relation(fields: [id_registratore], references: [id]) +} + model Registratore { id Int @id @default(autoincrement()) id_cliente Int + seriale String? @db.VarChar(255) + data_acquisto DateTime? @db.Date + ultima_verifica DateTime? @db.Date + prossima_verifica DateTime? @db.Date cliente Cliente @relation(fields: [id_cliente], references: [id]) + interventi Intervento[] } model Cliente { diff --git a/src/app/api/clienti/[id]/route.ts b/src/app/api/clienti/[id]/route.ts new file mode 100644 index 0000000..e352054 --- /dev/null +++ b/src/app/api/clienti/[id]/route.ts @@ -0,0 +1,24 @@ +import { PrismaClient } from "@/generated/prisma"; + +export async function GET( + request: Request, + { params }: { params: Promise<{ id: number }> }, +) { + const prisma = new PrismaClient(); + const { id }: { id: number } = await params; + + const cliente = await prisma.cliente.findUnique({ + where: { + id: Number(id), + }, + include: { + registratori: { + include: { + interventi: true, + }, + }, + }, + }); + + return Response.json({ cliente }); +} diff --git a/src/app/api/clienti/route.ts b/src/app/api/clienti/route.ts new file mode 100644 index 0000000..6ff3c19 --- /dev/null +++ b/src/app/api/clienti/route.ts @@ -0,0 +1,47 @@ +import { PrismaClient } from "@/generated/prisma"; + +export async function GET(request: Request) { + const prisma = new PrismaClient(); + + const clients = await prisma.cliente.findMany({ + include: { + registratori: { + include: { + interventi: true, + }, + }, + }, + }); + + return Response.json({ clients }); +} + +export async function POST(request: Request) { + const prisma = new PrismaClient(); + const data = await request.json(); + + await prisma.cliente.create({ + data: { + contratto: data.contratto, + email: data.email, + partita_iva: data.partita_iva, + ragione_sociale: data.ragione_sociale, + sede: data.sede, + sede_url: data.sede_url, + telefono: data.telefono, + registratori: { + create: { + interventi: { + create: { + data: new Date("2025-10-30"), + fattura: true, + lavoro: "Boh, cazzeggiato tutto il tempo", + }, + }, + }, + }, + }, + }); + + return Response.json({ message: "fatto bastardo" }); +} diff --git a/src/app/api/interventi/route.ts b/src/app/api/interventi/route.ts new file mode 100644 index 0000000..6b3af5c --- /dev/null +++ b/src/app/api/interventi/route.ts @@ -0,0 +1,17 @@ +import { PrismaClient } from "@/generated/prisma"; + +export async function POST(request: Request) { + const prisma = new PrismaClient(); + const data = await request.json(); + + await prisma.intervento.create({ + data: { + id_registratore: data.id, + data: new Date(data.data), + lavoro: data.lavoro, + fattura: data.fattura, + }, + }); + + return Response.json({ message: "fatto bastardo" }); +} diff --git a/src/app/api/registratori/interventi/[id]/route.ts b/src/app/api/registratori/interventi/[id]/route.ts new file mode 100644 index 0000000..1e4429e --- /dev/null +++ b/src/app/api/registratori/interventi/[id]/route.ts @@ -0,0 +1,41 @@ +import { PrismaClient } from "@/generated/prisma"; + +export async function GET( + request: Request, + { params }: { params: Promise<{ id: number }> }, +) { + const prisma = new PrismaClient(); + const { id }: { id: number } = await params; + + const cliente = await prisma.intervento.findMany({ + where: { + id_registratore: Number(id), + }, + }); + + return Response.json({ cliente }); +} + +export async function POST( + request: Request, + { params }: { params: Promise<{ id: number }> }, +) { + const prisma = new PrismaClient(); + const { id }: { id: number } = await params; + + + await prisma.intervento.create({ + data: { + id_registratore: id, + data: + } + }) + + const cliente = await prisma.intervento.findMany({ + where: { + id_registratore: Number(id), + }, + }); + + return Response.json({ cliente }); +} diff --git a/src/app/client/page.tsx b/src/app/client/page.tsx index f461635..76d4f1e 100644 --- a/src/app/client/page.tsx +++ b/src/app/client/page.tsx @@ -2,11 +2,26 @@ import ClientCard from "@/components/client-card"; import DeviceCard from "@/components/device-card"; +import { Cliente, Intervento, Registratore } from "@/generated/prisma"; import { useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; export default function Page() { const searchParams = useSearchParams(); - const client = searchParams.get("client"); + const id = searchParams.get("client"); + const [cliente, setCliente] = useState(); + const [registratori, setRegistratori] = useState>(); + + useEffect(() => { + async function getCliente() { + const req = await fetch(`/api/clienti/${id}`); + const data = await req.json(); + setCliente(data.cliente); + setRegistratori(data.cliente.registratori); + } + + getCliente(); + }, [id]); const clienti = [ { @@ -90,9 +105,15 @@ export default function Page() { return (
- - + {cliente ? : <>} + {registratori ? ( + registratori.map((registratore) => ( + + )) + ) : ( + <> + )}
); -} \ No newline at end of file +} diff --git a/src/components/add-client.tsx b/src/components/add-client.tsx new file mode 100644 index 0000000..ae38bf2 --- /dev/null +++ b/src/components/add-client.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; + +const AddClientDialog = () => { + const [nome, setNome] = useState(""); + const [ragione_sociale, setRagione_sociale] = useState(""); + const [partita_iva, setPartita_iva] = useState(""); + const [telefono, setTelefono] = useState(""); + const [email, setEmail] = useState(""); + const [sede, setSede] = useState(""); + const [sede_url, setSede_url] = useState(""); + const [contratto, setContratto] = useState(""); + + return ( +
+ +
+ + + + + + Aggiungi cliente + {/* + Make changes to your profile here. Click save when you're + done. + */} + +
+
+ + setNome(e.target.value)} + /> +
+
+ + setRagione_sociale(e.target.value)} + /> +
+
+ + setPartita_iva(e.target.value)} + /> +
+
+ + setTelefono(e.target.value)} + /> +
+
+ + setEmail(e.target.value)} + /> +
+
+ + setSede(e.target.value)} + /> +
+
+ + setSede_url(e.target.value)} + /> +
+
+ + setContratto(e.target.value)} + /> +
+
+ + + + + + +
+
+
+
+ ); +}; + +export default AddClientDialog; diff --git a/src/components/add-intervento.tsx b/src/components/add-intervento.tsx new file mode 100644 index 0000000..8efdb00 --- /dev/null +++ b/src/components/add-intervento.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Plus } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; +import DatePicker from "./date-picker"; +import { Checkbox } from "@/components/ui/checkbox"; + +const AddInterventoDialog = ({ id }: { id: Number }) => { + //const [nome, setNome] = useState(""); + const [open, setOpen] = useState(false); + const [data, setData] = useState(); + const [lavoro, setLavoro] = useState(""); + const [fattura, setFattura] = useState(); + + return ( +
+ +
+ + + + + + + +

Aggiungi intervento

+
+
+ + + Aggiungi intervento + {/* + Make changes to your profile here. Click save when you're + done. + */} + +
+
+ + +
+
+ + setLavoro(e.target.value)} + /> +
+
+ + setFattura( + checked === "indeterminate" + ? false + : checked + ? true + : true, + ) + } + /> + +
+
+ + + + + + +
+
+
+
+ ); +}; + +export default AddInterventoDialog; diff --git a/src/components/add-registratore.tsx b/src/components/add-registratore.tsx new file mode 100644 index 0000000..0d3a330 --- /dev/null +++ b/src/components/add-registratore.tsx @@ -0,0 +1,147 @@ +"use client"; + +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Plus } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; +import DatePicker from "./date-picker"; +import { Checkbox } from "@/components/ui/checkbox"; +import ModelPicker from "./model-picker"; + +const AddRegistratoreDialog = ({ id }: { id: Number }) => { + //const [nome, setNome] = useState(""); + const [openData, setOpenData] = useState(false); + const [openModello, setOpenModello] = useState(false); + const [data, setData] = useState(); + const [modello, setModello] = useState(); + const [lavoro, setLavoro] = useState(""); + const [fattura, setFattura] = useState(); + + const modelli = [ + { + value: "Form 100", + label: "Form 100", + }, + { + value: "Form 200", + label: "Form 200", + }, + { + value: "Form 200 Plus", + label: "Form 200 Plus", + }, + { + value: "Form 500", + label: "Form 500", + }, + ]; + + return ( +
+ +
+ + + + + + + +

Aggiungi registratore

+
+
+ + + Aggiungi registratore + +
+
+ + setLavoro(e.target.value)} + /> +
+
+ + +
+
+ + +
+
+ + setFattura( + checked === "indeterminate" + ? false + : checked + ? true + : true, + ) + } + /> + +
+
+ + + + + + +
+
+
+
+ ); +}; + +export default AddRegistratoreDialog; diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 36a987b..ef744d5 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -26,18 +26,8 @@ import { } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; import { XIcon } from "lucide-react"; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" +import AddClientDialog from "./add-client"; +import { Cliente } from "@/generated/prisma"; const data = { user: { @@ -68,8 +58,8 @@ const data = { url: "/client", icon: Home, isVisible: false, - } - ], + }, + ] /* clienti: [ { name: "Savoldi Ettore", @@ -141,16 +131,26 @@ const data = { }, ], }, - ], + ],*/, }; export function AppSidebar({ ...props }: React.ComponentProps) { const pathname = usePathname(); const router = useRouter(); const [clientPathname, setClientPathname] = useState(""); - const [clienti, setClienti] = React.useState(data.clienti); + const [clienti, setClienti] = useState>(); const { setOpen } = useSidebar(); + useEffect(() => { + (async () => { + const data = await fetch("/api/clienti"); + const x = await data.json(); + console.log(x.clients); + setClienti(x.clients); + console.log(clienti); + })(); + }, []); + useEffect(() => { setClientPathname(pathname); }, [pathname]); @@ -207,34 +207,25 @@ export function AppSidebar({ ...props }: React.ComponentProps) { return; } return ( - - { - const clienti = data.clienti.sort( - () => Math.random() - 0.5, - ); - setClienti( - clienti.slice( - 0, - Math.max(5, Math.floor(Math.random() * 10) + 1), - ), - ); - setOpen(true); - if (clientPathname != item.url) { - router.push(item.url); - } - }} - isActive={clientPathname === item.url} - className="px-2.5 md:px-2" - > - - {item.title} - - + + { + setOpen(true); + if (clientPathname != item.url) { + router.push(item.url); + } + }} + isActive={clientPathname === item.url} + className="px-2.5 md:px-2" + > + + {item.title} + + ); })} @@ -257,99 +248,53 @@ export function AppSidebar({ ...props }: React.ComponentProps) {
- + - + }} + > + + -

Resetta ricerca

+

Resetta ricerca

-
- -
- - - - - - Aggiungi cliente - {/* - Make changes to your profile here. Click save when you're - done. - */} - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - - - - - -
-
-
-
+ - {clienti.map((cliente) => ( + {clienti?.map((cliente) => ( { let path = clientPathname.split("/")[1]; - if (path == "dashboard") { - path = "client" + if (path == "dashboard" || path == "") { + path = "client"; } - router.push(`/${path}?client=${cliente.name}`) + router.push(`/${path}?client=${cliente.id}`); }} - key={cliente.name} + key={cliente.id} className="hover:cursor-pointer w-11/12 mx-auto rounded-md hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex flex-col items-start gap-2 border-b p-4 text-sm leading-tight whitespace-nowrap last:border-b-0" > - {cliente.name} + {cliente.ragione_sociale}
- {cliente.sede} + + {cliente.sede.length > 35 + ? cliente.sede.substring(0, 35) + "..." + : cliente.sede} +
- - {cliente.registratori[0].prossima_verifica} -
))}
diff --git a/src/components/client-card.tsx b/src/components/client-card.tsx index 6bc560c..2611322 100644 --- a/src/components/client-card.tsx +++ b/src/components/client-card.tsx @@ -1,96 +1,81 @@ -import { - Card, - CardAction, - CardContent, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { Button } from "@/components/ui/button"; -import { Edit, Plus } from "lucide-react"; - -const ClientCard = ({client}: {client: any}) => { - return ( - - - - {client} - - - - - - - -

Modifica dettagli

-
-
- - - - - -

Aggiungi registratore

-
-
-
-
- -
-
Ragione Sociale
-
- Acconciature Uomo -
-
-
-
Partita IVA
-
- 13407520172 -
-
-
-
- Numero di Telefono -
-
- 0301547854 -
-
- -
-
Contratto
- -
-
-
- ) -} - -export default ClientCard; \ No newline at end of file +import { + Card, + CardAction, + CardContent, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Button } from "@/components/ui/button"; +import { Edit, Plus } from "lucide-react"; +import { Cliente } from "@/generated/prisma"; +import AddRegistratoreDialog from "./add-registratore"; + +const ClientCard = ({ cliente }: { cliente: Cliente }) => { + return ( + + + + {cliente.ragione_sociale} + + + + + + + +

Modifica dettagli

+
+
+ +
+
+ +
+
Partita IVA
+
+ {cliente.partita_iva} +
+
+
+
Numero di Telefono
+
+ {cliente.telefono} +
+
+
+
Sede
+ +
+
+
Contratto
+ +
+
+
+ ); +}; + +export default ClientCard; diff --git a/src/components/date-picker.tsx b/src/components/date-picker.tsx new file mode 100644 index 0000000..8f309dc --- /dev/null +++ b/src/components/date-picker.tsx @@ -0,0 +1,49 @@ +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Calendar } from "@/components/ui/calendar"; +import { Button } from "@/components/ui/button"; +import { ChevronDownIcon } from "lucide-react"; +import { Dispatch, SetStateAction } from "react"; + +const DatePicker = ({ + open, + setOpen, + date, + setDate, +}: { + open: boolean; + setOpen: Dispatch>; + date: Date | undefined; + setDate: Dispatch>; +}) => { + return ( + + + + + + { + setDate(date); + setOpen(false); + }} + /> + + + ); +}; + +export default DatePicker; diff --git a/src/components/device-card.tsx b/src/components/device-card.tsx index 804f670..109e07b 100644 --- a/src/components/device-card.tsx +++ b/src/components/device-card.tsx @@ -1,169 +1,127 @@ -import { - Card, - CardAction, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { - Table, - TableBody, - TableCaption, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { Button } from "@/components/ui/button"; -import { Edit, Plus } from "lucide-react"; -import { Checkbox } from "@/components/ui/checkbox"; -import { cn } from "@/lib/utils"; -import { useSearchParams } from "next/navigation"; - -type Invervento = { - id: string, - data: string, - lavoro: string, - fattura: boolean -} - -type Registratore = { - seriale: string, - acquisto: string, - ultima_verifica: string, - prossima_verifica: string, - interventi: Array -} - -type Cliente = { - name: string, - email: string, - ragione_sociale: string, - p_iva: string, - telefono: string, - sede: string, - sede_url: string, - contratto: string, - registratori: Array -} - -const DeviceCard = ({clienti}: {clienti:Array}) => { - const searchParams = useSearchParams(); - const client = searchParams.get("client"); - - return ( - - - - FORM 100 - - - - - - - -

Modifica dettagli

-
-
- - - - - -

Aggiungi intervento

-
-
-
-
- -
-
-
Seriale
-
- 80E100548745 -
-
-
-
Data acquisto
-
- 15/10/2019 -
-
-
-
Ultima verifica
-
- 15/10/2025 -
-
-
-
- Prossima verifica -
-
- 15/10/2026 -
-
-
-
- - - Lista interventi - - - - Data - Lavoro - Fattura - - - - {clienti - .find( - (cliente) => - cliente.name == decodeURIComponent(client?.toString() || ""), - ) - ?.registratori[0].interventi.map((intervento) => ( - - - {intervento.data} - - {intervento.lavoro} - - - - - ))} - -
-
-
- - - - -
- ) -} - -export default DeviceCard; \ No newline at end of file +"use client"; + +import { + Card, + CardAction, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Button } from "@/components/ui/button"; +import { Edit, Plus } from "lucide-react"; +import { Checkbox } from "@/components/ui/checkbox"; +import { cn } from "@/lib/utils"; +import { useSearchParams } from "next/navigation"; +import { Cliente, Registratore } from "@/generated/prisma"; +import AddInterventoDialog from "./add-intervento"; + +const DeviceCard = ({ registratore }: { registratore: Registratore }) => { + return ( + + + + FORM 100 + + + + + + + +

Modifica dettagli

+
+
+ +
+
+ +
+
+
Seriale
+
+ {registratore.id} +
+
+
+
Data acquisto
+
+ {registratore.data_acquisto + ? new Date(registratore.data_acquisto).toLocaleDateString( + "it-IT", + ) + : "--"} +
+
+
+
Ultima verifica
+
+ {registratore.ultima_verifica + ? new Date(registratore.ultima_verifica).toLocaleDateString( + "it-IT", + ) + : "--"} +
+
+
+
Prossima verifica
+
+ {registratore.prossima_verifica + ? new Date(registratore.prossima_verifica).toLocaleDateString( + "it-IT", + ) + : "--"} +
+
+
+
+ + + Lista interventi + + + + Data + Lavoro + Fattura + + + +
+
+
+ + + + +
+ ); +}; + +export default DeviceCard; diff --git a/src/components/model-picker.tsx b/src/components/model-picker.tsx new file mode 100644 index 0000000..2bfe16f --- /dev/null +++ b/src/components/model-picker.tsx @@ -0,0 +1,79 @@ +import { Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Dispatch, SetStateAction } from "react"; + +const ModelPicker = ({ + open, + setOpen, + value, + setValue, + modelli, +}: { + open: boolean; + setOpen: Dispatch>; + value: any; + setValue: Dispatch>; + modelli: Array; +}) => { + return ( + + + + + + + + + No framework found. + + {modelli.map((modello) => ( + { + setValue(currentValue === value ? "" : currentValue); + setOpen(false); + }} + > + {modello.label} + + + ))} + + + + + + ); +}; + +export default ModelPicker; diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx new file mode 100644 index 0000000..6f304b5 --- /dev/null +++ b/src/components/ui/calendar.tsx @@ -0,0 +1,216 @@ +"use client" + +import * as React from "react" +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "lucide-react" +import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker" + +import { cn } from "@/lib/utils" +import { Button, buttonVariants } from "@/components/ui/button" + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = "label", + buttonVariant = "ghost", + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps["variant"] +}) { + const defaultClassNames = getDefaultClassNames() + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString("default", { month: "short" }), + ...formatters, + }} + classNames={{ + root: cn("w-fit", defaultClassNames.root), + months: cn( + "flex gap-4 flex-col md:flex-row relative", + defaultClassNames.months + ), + month: cn("flex flex-col w-full gap-4", defaultClassNames.month), + nav: cn( + "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", + defaultClassNames.nav + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_previous + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_next + ), + month_caption: cn( + "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", + defaultClassNames.month_caption + ), + dropdowns: cn( + "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", + defaultClassNames.dropdowns + ), + dropdown_root: cn( + "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", + defaultClassNames.dropdown_root + ), + dropdown: cn( + "absolute bg-popover inset-0 opacity-0", + defaultClassNames.dropdown + ), + caption_label: cn( + "select-none font-medium", + captionLayout === "label" + ? "text-sm" + : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", + defaultClassNames.caption_label + ), + table: "w-full border-collapse", + weekdays: cn("flex", defaultClassNames.weekdays), + weekday: cn( + "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", + defaultClassNames.weekday + ), + week: cn("flex w-full mt-2", defaultClassNames.week), + week_number_header: cn( + "select-none w-(--cell-size)", + defaultClassNames.week_number_header + ), + week_number: cn( + "text-[0.8rem] select-none text-muted-foreground", + defaultClassNames.week_number + ), + day: cn( + "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", + props.showWeekNumber + ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md" + : "[&:first-child[data-selected=true]_button]:rounded-l-md", + defaultClassNames.day + ), + range_start: cn( + "rounded-l-md bg-accent", + defaultClassNames.range_start + ), + range_middle: cn("rounded-none", defaultClassNames.range_middle), + range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), + today: cn( + "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + defaultClassNames.today + ), + outside: cn( + "text-muted-foreground aria-selected:text-muted-foreground", + defaultClassNames.outside + ), + disabled: cn( + "text-muted-foreground opacity-50", + defaultClassNames.disabled + ), + hidden: cn("invisible", defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ) + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === "left") { + return ( + + ) + } + + if (orientation === "right") { + return ( + + ) + } + + return ( + + ) + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ) + }, + ...components, + }} + {...props} + /> + ) +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames() + + const ref = React.useRef(null) + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus() + }, [modifiers.focused]) + + return ( +