diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c550055 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +.git diff --git a/.gitignore b/.gitignore index 5ef6a52..8051b69 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +/prisma/generated/prisma 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/Dockerfile b/Dockerfile index d9d1abf..2075f52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,38 +1,44 @@ -# Fase 1: Build -FROM node:18-alpine AS builder +FROM node:22-alpine AS base + +RUN apk add --no-cache libc6-compat openssl -# Imposta la directory di lavoro WORKDIR /app -# Copia i file di configurazione e dipendenze -COPY package.json pnpm-lock.yaml* ./ +# --- STAGE 1 --- +FROM base AS deps +COPY package.json ./ +RUN npm install -# Installa le dipendenze -RUN npm install --force - -# Copia il resto dei file dell'app +# --- STAGE 2 --- +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules COPY . . -# Build dell'app Next.js +RUN npx prisma generate + +ENV NEXT_TELEMETRY_DISABLED=1 + RUN npm run build -# Fase 2: Runtime (immagine più leggera) -FROM node:18-alpine AS runner - -# Imposta la directory di lavoro +# --- STAGE 3 --- +FROM base AS runner WORKDIR /app -# Imposta variabile d'ambiente per produzione ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs -# Copia solo ciò che serve per l'esecuzione COPY --from=builder /app/public ./public -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/package.json ./package.json -# Porta esposta (3000 di default per Next.js) +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + EXPOSE 3000 -# Comando di avvio -CMD ["npm", "start"] +CMD ["node", "server.js"] diff --git a/docker-compose.yml b/docker-compose.yml index e3ebf5f..e1abaa5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,27 @@ services: - app: - container_name: dashregistratori - build: - context: . + webapp: + build: . + container_name: dash-registratori restart: unless-stopped ports: - - 3001:3000 - dns: 1.1.1.1 + - "3000:3000" environment: - NODE_ENV: development + DATABASE_URL: "postgresql://postgres:postgres@db:5432/db?schema=public" + depends_on: + - db + + db: + image: postgres:alpine + container_name: db + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: db + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: diff --git a/next.config.ts b/next.config.ts index 68a6c64..f4ba14f 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,14 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", + + typescript: { + ignoreBuildErrors: true, + }, + + eslint: { + ignoreDuringBuilds: true, + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 61556ce..03ee893 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,23 +8,29 @@ "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" @@ -35,6 +41,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" @@ -53,15 +60,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "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/@floating-ui/core": { "version": "1.7.3", @@ -111,405 +114,6 @@ "node": ">=18" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz", - "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.3" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz", - "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.3" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz", - "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz", - "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz", - "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz", - "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz", - "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz", - "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz", - "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz", - "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz", - "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz", - "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.3" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz", - "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.3" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz", - "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.3" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz", - "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.3" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz", - "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.3" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz", - "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz", - "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.3" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz", - "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.5.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz", - "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz", - "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, "node_modules/@img/sharp-win32-x64": { "version": "0.34.4", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz", @@ -605,7 +209,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -621,7 +224,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -637,7 +239,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -653,7 +254,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -669,7 +269,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -685,7 +284,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -701,7 +299,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -726,6 +323,91 @@ "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.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.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "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.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.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.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.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.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" + } + }, + "node_modules/@prisma/get-platform": { + "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.18.0" + } + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -868,6 +550,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", @@ -934,6 +634,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", @@ -1126,6 +844,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", @@ -1229,6 +1020,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", @@ -1284,9 +1093,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" @@ -1364,6 +1173,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", @@ -1558,6 +1385,13 @@ "react-dom": "^19.0.0" } }, + "node_modules/@standard-schema/spec": { + "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": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1902,6 +1736,35 @@ "node": ">=10" } }, + "node_modules/c12": { + "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", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001750", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", @@ -1922,6 +1785,22 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chokidar": { + "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" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -1932,6 +1811,16 @@ "node": ">=18" } }, + "node_modules/citty": { + "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" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -1959,6 +1848,39 @@ "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" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -1966,6 +1888,46 @@ "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" + } + }, + "node_modules/defu": { + "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": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1982,6 +1944,40 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dotenv": { + "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" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/effect": { + "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", + "fast-check": "^3.23.1" + } + }, + "node_modules/empathic": { + "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" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -1996,6 +1992,36 @@ "node": ">=10.13.0" } }, + "node_modules/exsolve": { + "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", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -2005,6 +2031,24 @@ "node": ">=6" } }, + "node_modules/giget": { + "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", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2016,7 +2060,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -2063,195 +2107,6 @@ "lightningcss-win32-x64-msvc": "1.30.1" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", @@ -2413,12 +2268,72 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-fetch-native": { + "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", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/ohash": { + "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": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/pkg-types": { + "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", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -2448,6 +2363,60 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prisma": { + "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.18.0", + "@prisma/engines": "6.18.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pure-rand": { + "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", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/rc9": { + "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", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -2457,6 +2426,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", @@ -2552,6 +2542,20 @@ } } }, + "node_modules/readdirp": { + "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" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -2678,11 +2682,11 @@ } }, "node_modules/tar": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", - "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -2694,6 +2698,13 @@ "node": ">=18" } }, + "node_modules/tinyexec": { + "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": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2714,7 +2725,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 8fcf4dd..c779a1f 100644 --- a/package.json +++ b/package.json @@ -8,23 +8,29 @@ "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" @@ -35,6 +41,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/20251111124743_002/migration.sql b/prisma/migrations/20251111124743_002/migration.sql new file mode 100644 index 0000000..72397aa --- /dev/null +++ b/prisma/migrations/20251111124743_002/migration.sql @@ -0,0 +1,21 @@ +/* + Warnings: + + - The primary key for the `Registratore` table will be changed. If it partially fails, the table could be left without primary key constraint. + +*/ +-- DropForeignKey +ALTER TABLE "public"."Intervento" DROP CONSTRAINT "Intervento_id_registratore_fkey"; + +-- AlterTable +ALTER TABLE "Intervento" ALTER COLUMN "id_registratore" SET DATA TYPE TEXT; + +-- AlterTable +ALTER TABLE "Registratore" DROP CONSTRAINT "Registratore_pkey", +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "Registratore_pkey" PRIMARY KEY ("id"); +DROP SEQUENCE "Registratore_id_seq"; + +-- AddForeignKey +ALTER TABLE "Intervento" ADD CONSTRAINT "Intervento_id_registratore_fkey" FOREIGN KEY ("id_registratore") REFERENCES "Registratore"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20251111124854_003/migration.sql b/prisma/migrations/20251111124854_003/migration.sql new file mode 100644 index 0000000..98c623b --- /dev/null +++ b/prisma/migrations/20251111124854_003/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `seriale` on the `Registratore` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Registratore" DROP COLUMN "seriale"; diff --git a/prisma/migrations/20251111125913_004/migration.sql b/prisma/migrations/20251111125913_004/migration.sql new file mode 100644 index 0000000..be235ee --- /dev/null +++ b/prisma/migrations/20251111125913_004/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "Modello" AS ENUM ('FORM100', 'FORM200', 'FORM500'); + +-- AlterTable +ALTER TABLE "Registratore" ADD COLUMN "modello" "Modello"; diff --git a/prisma/migrations/20251111130324_005/migration.sql b/prisma/migrations/20251111130324_005/migration.sql new file mode 100644 index 0000000..5082af6 --- /dev/null +++ b/prisma/migrations/20251111130324_005/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "Modello" ADD VALUE 'FORM200PLUS'; diff --git a/prisma/migrations/20251111140337_006/migration.sql b/prisma/migrations/20251111140337_006/migration.sql new file mode 100644 index 0000000..3a496fe --- /dev/null +++ b/prisma/migrations/20251111140337_006/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Cliente" ADD COLUMN "coordinate" DOUBLE PRECISION[]; diff --git a/prisma/migrations/20251111161412_007/migration.sql b/prisma/migrations/20251111161412_007/migration.sql new file mode 100644 index 0000000..bdec736 --- /dev/null +++ b/prisma/migrations/20251111161412_007/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `coordinate` on the `Cliente` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Cliente" DROP COLUMN "coordinate", +ADD COLUMN "lat" DOUBLE PRECISION, +ADD COLUMN "lon" DOUBLE PRECISION; diff --git a/prisma/migrations/20251111164927_008/migration.sql b/prisma/migrations/20251111164927_008/migration.sql new file mode 100644 index 0000000..7a4e906 --- /dev/null +++ b/prisma/migrations/20251111164927_008/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - Made the column `lat` on table `Cliente` required. This step will fail if there are existing NULL values in that column. + - Made the column `lon` on table `Cliente` required. This step will fail if there are existing NULL values in that column. + +*/ +-- AlterTable +ALTER TABLE "Cliente" ALTER COLUMN "lat" SET NOT NULL, +ALTER COLUMN "lon" SET NOT NULL; diff --git a/prisma/migrations/20251120094202_009/migration.sql b/prisma/migrations/20251120094202_009/migration.sql new file mode 100644 index 0000000..5539634 --- /dev/null +++ b/prisma/migrations/20251120094202_009/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Cliente" ALTER COLUMN "sede_url" SET DATA TYPE TEXT; 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 new file mode 100644 index 0000000..d078cd7 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,50 @@ +generator client { + provider = "prisma-client-js" + output = "../src/generated/prisma" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +enum Modello { + FORM100 + FORM200 + FORM200PLUS + FORM500 +} + +model Intervento { + id Int @id @default(autoincrement()) + id_registratore String + data DateTime @db.Date + lavoro String @db.Text + fattura Boolean @db.Boolean + registratore Registratore @relation(fields: [id_registratore], references: [id]) +} + +model Registratore { + id String @id + id_cliente Int + modello Modello? + 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 { + id Int @id @default(autoincrement()) + ragione_sociale String @unique @db.VarChar(255) + email String @db.VarChar(255) + partita_iva String @db.VarChar(255) + telefono String @db.VarChar(255) + sede String @db.VarChar(255) + sede_url String + contratto String @db.VarChar(255) + lat Float + lon Float + registratori Registratore[] +} diff --git a/src/app/api/clienti/[id]/route.ts b/src/app/api/clienti/[id]/route.ts new file mode 100644 index 0000000..1e3b056 --- /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: string }> }, +) { + const prisma = new PrismaClient(); + const { id } = 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..dd83a96 --- /dev/null +++ b/src/app/api/clienti/route.ts @@ -0,0 +1,66 @@ +import { Modello, 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 }); +} + +function extractLatLonFromGmapsUrl(url: string) { + // Usa una regex per cercare la latitudine e la longitudine nell'URL + const regex = /@(-?\d+\.\d+),(-?\d+\.\d+)/; + const match = url.match(regex); + + if (match) { + const lat = parseFloat(match[1]); + const lon = parseFloat(match[2]); + return [lat, lon]; // Restituisce un array con latitudine e longitudine + } else { + return [0, 0]; // Se non trova latitudine e longitudine + } +} + +export async function POST(request: Request) { + const prisma = new PrismaClient(); + const data = await request.json(); + const [lat, lon] = extractLatLonFromGmapsUrl(data.sede_url); + + 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, + lat: lat, + lon: lon, + /*registratori: { + create: { + id: "80E1758414", + modello: Modello.FORM100, + 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..b4184c7 --- /dev/null +++ b/src/app/api/registratori/interventi/[id]/route.ts @@ -0,0 +1,17 @@ +import { PrismaClient } from "@/generated/prisma"; + +export async function GET( + request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + const prisma = new PrismaClient(); + const { id }: { id: string } = await params; + + const cliente = await prisma.intervento.findMany({ + where: { + id_registratore: id, + }, + }); + + return Response.json({ cliente }); +} diff --git a/src/app/api/registratori/route.ts b/src/app/api/registratori/route.ts new file mode 100644 index 0000000..6c6bbd8 --- /dev/null +++ b/src/app/api/registratori/route.ts @@ -0,0 +1,24 @@ +import { Modello, PrismaClient } from "@/generated/prisma"; + +export async function POST(request: Request) { + const prisma = new PrismaClient(); + const data = await request.json(); + + await prisma.registratore.create({ + data: { + id: data.seriale, + id_cliente: data.id, + data_acquisto: new Date(data.data), + modello: + data.modello === "FORM100" + ? Modello.FORM100 + : data.modello === "FORM200" + ? Modello.FORM200 + : data.modello === "FORM200PLUS" + ? Modello.FORM200PLUS + : Modello.FORM500, + }, + }); + + return Response.json({ message: "fatto bastardo" }); +} diff --git a/src/app/client/page.tsx b/src/app/client/page.tsx index f461635..047838d 100644 --- a/src/app/client/page.tsx +++ b/src/app/client/page.tsx @@ -2,97 +2,49 @@ 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, Suspense } from "react"; -export default function Page() { +function ClientComponent() { const searchParams = useSearchParams(); - const client = searchParams.get("client"); + const id = searchParams.get("client"); + const [cliente, setCliente] = useState(); + const [registratori, setRegistratori] = useState>(); - const clienti = [ - { - name: "Savoldi Ettore", - email: "savoldi.ettore@gmail.com", - ragione_sociale: "Acconciature Uomo", - p_iva: "13407520172", - telefono: "0301547854", - sede: "Via Umberto I 60/T, Flero (BS)", - sede_url: "https://maps.app.goo.gl/9uNbw2a62ZCCjkQc7", - contratto: "https://google.com", - registratori: [ - { - seriale: "80E100548745", - acquisto: "15/10/2019", - ultima_verifica: "15/10/2025", - prossima_verifica: "15/10/2026", - interventi: [ - { - id: "0001", - data: "15/10/2025", - lavoro: "VERIFICA FISCALE - AGGIORNAMENTO FIRMWARE", - fattura: true, - }, - { - id: "0002", - data: "28/05/2025", - lavoro: "SOSTITUZIONE DGFE", - fattura: false, - }, - { - id: "0003", - data: "08/10/2024", - lavoro: "VERIFICA FISCALE", - fattura: true, - }, - ], - }, - ], - }, - { - name: "Forno Tagliaferri", - email: "info@tagliaferri.it", - ragione_sociale: "Forno Tagliaferri", - p_iva: "12901475639", - telefono: "0309183573", - sede: "Via Corso dei Martiri 11, Brescia (BS)", - sede_url: "https://maps.app.goo.gl/9uNbw2a62ZCCjkQc7", - contratto: "https://google.com", - registratori: [ - { - seriale: "80E1002587545", - acquisto: "24/02/2020", - ultima_verifica: "24/02/2025", - prossima_verifica: "24/02/2026", - interventi: [ - { - id: "0004", - data: "24/02/2025", - lavoro: "VERIFICA FISCALE", - fattura: true, - }, - { - id: "0005", - data: "06/04/2025", - lavoro: "SOSTITUZIONE DGFE", - fattura: false, - }, - { - id: "0006", - data: "24/02/2025", - lavoro: "VERIFICA FISCALE - AGGIORNAMENTO FIRMWARE", - fattura: true, - }, - ], - }, - ], - }, - ]; + 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]); return (
- - + {cliente ? : <>} + {registratori ? ( + registratori.map((registratore) => ( + + )) + ) : ( + <> + )}
); -} \ No newline at end of file +} + +export default function Page() { + return ( + Caricamento cliente...} + > + + + ); +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 5dab631..9e4b221 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -28,7 +28,6 @@ import { } from "@/components/ui/table"; import { cn } from "@/lib/utils"; import { Checkbox } from "@/components/ui/checkbox"; -import { useSearchParams } from "next/navigation"; export default function Page() { return ( diff --git a/src/app/map/page.tsx b/src/app/map/page.tsx index c26b800..4b5eda1 100644 --- a/src/app/map/page.tsx +++ b/src/app/map/page.tsx @@ -2,18 +2,41 @@ import dynamic from "next/dynamic"; import { useSearchParams } from "next/navigation"; +import { useEffect, useState, Suspense } from "react"; +import { Cliente } from "@/generated/prisma"; -export default function Page() { +function MapContent() { const MapWithNoSSR = dynamic(() => import("../../components/map"), { ssr: false, }); - + const [clienti, setClienti] = useState>(); const searchParams = useSearchParams(); const client = searchParams.get("client"); + useEffect(() => { + async function getClienti() { + const req = await fetch(`/api/clienti`); + const data = await req.json(); + setClienti(data.clients); + } + + getClienti(); + }, []); + return (
- + cliente.id == +(client || -1))} + clienti={clienti || undefined} + />
); } + +export default function Page() { + return ( + Caricamento mappa...}> + + + ); +} diff --git a/src/components/add-client.tsx b/src/components/add-client.tsx new file mode 100644 index 0000000..cc65731 --- /dev/null +++ b/src/components/add-client.tsx @@ -0,0 +1,153 @@ +"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 [open, setOpen] = useState(false); + 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..eeb9782 --- /dev/null +++ b/src/components/add-intervento.tsx @@ -0,0 +1,117 @@ +"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 { useEffect, useState } from "react"; +import DatePicker from "./date-picker"; +import { Checkbox } from "@/components/ui/checkbox"; + +const AddInterventoDialog = ({ id }: { id: string }) => { + //const [nome, setNome] = useState(""); + const [open, setOpen] = useState(false); + const [data, setData] = useState(new Date()); + const [lavoro, setLavoro] = useState(""); + const [fattura, setFattura] = useState(false); + + return ( +
+ +
+ + + + + + + +

Aggiungi intervento

+
+
+ + + Aggiungi intervento + {/* + Make changes to your profile here. Click save when you're + done. + */} + +
+
+ + +
+
+ + setLavoro(e.target.value)} + /> +
+
+ { + checked === "indeterminate" + ? setFattura(false) + : checked + ? setFattura(true) + : setFattura(false); + }} + /> + +
+
+ + + + + + +
+
+
+
+ ); +}; + +export default AddInterventoDialog; diff --git a/src/components/add-registratore.tsx b/src/components/add-registratore.tsx new file mode 100644 index 0000000..72f0da9 --- /dev/null +++ b/src/components/add-registratore.tsx @@ -0,0 +1,132 @@ +"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 [seriale, setSeriale] = useState(""); + const [modello, setModello] = useState(); + const [fattura, setFattura] = useState(); + + const modelli = [ + { + value: "FORM100", + label: "Form 100", + }, + { + value: "FORM200", + label: "Form 200", + }, + { + value: "FORM200PLUS", + label: "Form 200 Plus", + }, + { + value: "FORM500", + label: "Form 500", + }, + ]; + + return ( +
+ +
+ + + + + + + +

Aggiungi registratore

+
+
+ + + Aggiungi registratore + +
+
+ + setSeriale(e.target.value)} + /> +
+
+ + +
+
+ + +
+
+ + + + + + +
+
+
+
+ ); +}; + +export default AddRegistratoreDialog; diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 36a987b..8cba9e9 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,24 +131,30 @@ 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]); - useEffect(() => { - console.log(); - }, [clientPathname]); - return ( ) { 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 +244,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..5a9fc7f 100644 --- a/src/components/device-card.tsx +++ b/src/components/device-card.tsx @@ -1,169 +1,138 @@ -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 { Cliente, Registratore } from "@/generated/prisma"; +import AddInterventoDialog from "./add-intervento"; + +const DeviceCard = ({ registratore }: { registratore: Registratore }) => { + return ( + + + + {registratore.modello} + + + + + + + +

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 + + + + {registratore.interventi.map((intervento) => ( + + + {new Date(intervento.data).toLocaleDateString("it-IT")} + + {intervento.lavoro} + + + + + ))} + +
+
+
+ + + + +
+ ); +}; + +export default DeviceCard; diff --git a/src/components/map.tsx b/src/components/map.tsx index cd0b791..9c0b3e0 100644 --- a/src/components/map.tsx +++ b/src/components/map.tsx @@ -8,8 +8,15 @@ import { } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; import { Map as MapIcon } from "lucide-react"; +import { Cliente } from "@/generated/prisma"; -const Map = ({highlight}: {highlight?: string}) => { +const Map = ({ + highlight, + clienti, +}: { + highlight?: Cliente; + clienti?: Array; +}) => { const positions = [ { name: "Autogeneral", @@ -38,9 +45,6 @@ const Map = ({highlight}: {highlight?: string}) => { }, ]; - console.log(highlight); - console.log(positions.find((client) => client.name == highlight)); - const customMarker = new Icon({ iconUrl: "marker-icon-red.png", iconAnchor: [10, 20], @@ -49,7 +53,11 @@ const Map = ({highlight}: {highlight?: string}) => { return ( client.name == highlight)?.position : [45.54157745559809, 10.211896906975962]} + center={ + highlight + ? [highlight.lat, highlight.lon] + : [45.54157745559809, 10.211896906975962] + } zoom={13} scrollWheelZoom={true} className="m-0 p-0 h-[99%] w-[99%] mx-[0.5%] my-[0.5%] rounded-md z-0" @@ -58,9 +66,50 @@ const Map = ({highlight}: {highlight?: string}) => { attribution='© OpenStreetMap contributors' url={`https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`} /> - { highlight == null ? positions.map((client) => ( + {highlight == null ? ( + clienti ? ( + clienti.map((client) => ( + + +
+
+ + {client.ragione_sociale} + +
+ P.IVA + {client.partita_iva} +
+
+ + + + + + + + +

Resetta ricerca

+
+
+
+
+
+ )) + ) : ( + <> + ) + ) : ( {
- {client.name} + + {highlight.ragione_sociale} +
P.IVA 03417520172 @@ -77,52 +128,20 @@ const Map = ({highlight}: {highlight?: string}) => { - - - + + + -

Resetta ricerca

+

Resetta ricerca

- )) : - client.name == highlight)?.position} - draggable={false} - animate={false} - icon={customMarker} - > - -
-
- {positions.find((client) => client.name == highlight)?.name} -
- P.IVA - 03417520172 -
-
- - - - client.name == highlight)?.sede_url} target="blank"> - - - - -

Resetta ricerca

-
-
-
-
-
- } + )} ); }; 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 ( +