feat: add order editing form and payment processing pages
- Implemented FormEditOrder component for editing orders with validation and submission handling. - Created SchedaAnnuncioPage for displaying printable announcement details. - Added BonificoManualePage for manual bank transfer payment instructions. - Developed PagamentoPage for Stripe payment processing with PaymentIntent creation. - Introduced PreviewPurchasePage for preparing payment with service details. - Added pagamenti.controller for handling payment preparation and retrieval of payment data.
This commit is contained in:
parent
6211be289c
commit
6c36bb9b74
64 changed files with 2647 additions and 2546 deletions
28
apps/db/migrations/34_ordiniv2.up.sql
Normal file
28
apps/db/migrations/34_ordiniv2.up.sql
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
ALTER TABLE IF EXISTS public.servizio
|
||||||
|
ADD COLUMN "onboardOk" boolean NOT NULL DEFAULT FALSE;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.ordini
|
||||||
|
ADD COLUMN amount_cent integer NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.ordini ADD COLUMN paymentmethod TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.ordini
|
||||||
|
ADD COLUMN paid_at TIMESTAMP WITHOUT TIME ZONE;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.ordini ADD COLUMN intent_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.ordini
|
||||||
|
ADD COLUMN paymentstatus "PaymentStatusEnum";
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.ordini
|
||||||
|
ADD COLUMN condizioni TEXT NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.ordini
|
||||||
|
ADD COLUMN sconto integer NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
public.flags (id, VALUE)
|
||||||
|
VALUES (
|
||||||
|
'STRIPE_DISABLED'::TEXT,
|
||||||
|
FALSE::boolean
|
||||||
|
);
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
|
"$schema": "https://biomejs.dev/schemas/2.4.6/schema.json",
|
||||||
"assist": {
|
"assist": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"source": {
|
"source": {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ async function createNextConfig(): Promise<NextConfig> {
|
||||||
scrollRestoration: true,
|
scrollRestoration: true,
|
||||||
// esmExternals: "loose", // This if react-pdf gives compilation issues
|
// esmExternals: "loose", // This if react-pdf gives compilation issues
|
||||||
},
|
},
|
||||||
|
allowedDevOrigins: ["host.docker.internal"],
|
||||||
generateBuildId: async () => buildId,
|
generateBuildId: async () => buildId,
|
||||||
devIndicators: false,
|
devIndicators: false,
|
||||||
headers: async () => {
|
headers: async () => {
|
||||||
|
|
|
||||||
202
apps/infoalloggi/package-lock.json
generated
202
apps/infoalloggi/package-lock.json
generated
|
|
@ -80,7 +80,6 @@
|
||||||
"react-phone-number-input": "^3.4.14",
|
"react-phone-number-input": "^3.4.14",
|
||||||
"react-reverse-portal": "^2.3.0",
|
"react-reverse-portal": "^2.3.0",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"react-to-print": "^3.2.0",
|
|
||||||
"react-use": "^17.6.0",
|
"react-use": "^17.6.0",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"stripe": "^20.4.0",
|
"stripe": "^20.4.0",
|
||||||
|
|
@ -96,11 +95,9 @@
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.4.2",
|
"@biomejs/biome": "^2.4.6",
|
||||||
"@hookform/devtools": "^4.4.0",
|
"@hookform/devtools": "^4.4.0",
|
||||||
"@react-email/preview-server": "^5.2.8",
|
"@react-email/preview-server": "^5.2.8",
|
||||||
"@testing-library/dom": "^10.4.1",
|
|
||||||
"@testing-library/react": "^16.3.2",
|
|
||||||
"@total-typescript/ts-reset": "^0.6.1",
|
"@total-typescript/ts-reset": "^0.6.1",
|
||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"@types/node": "^24.2.0",
|
"@types/node": "^24.2.0",
|
||||||
|
|
@ -542,9 +539,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/biome": {
|
"node_modules/@biomejs/biome": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.6.tgz",
|
||||||
"integrity": "sha512-vVE/FqLxNLbvYnFDYg3Xfrh1UdFhmPT5i+yPT9GE2nTUgI4rkqo5krw5wK19YHBd7aE7J6r91RRmb8RWwkjy6w==",
|
"integrity": "sha512-QnHe81PMslpy3mnpL8DnO2M4S4ZnYPkjlGCLWBZT/3R9M6b5daArWMMtEfP52/n174RKnwRIf3oT8+wc9ihSfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -558,20 +555,20 @@
|
||||||
"url": "https://opencollective.com/biome"
|
"url": "https://opencollective.com/biome"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@biomejs/cli-darwin-arm64": "2.4.2",
|
"@biomejs/cli-darwin-arm64": "2.4.6",
|
||||||
"@biomejs/cli-darwin-x64": "2.4.2",
|
"@biomejs/cli-darwin-x64": "2.4.6",
|
||||||
"@biomejs/cli-linux-arm64": "2.4.2",
|
"@biomejs/cli-linux-arm64": "2.4.6",
|
||||||
"@biomejs/cli-linux-arm64-musl": "2.4.2",
|
"@biomejs/cli-linux-arm64-musl": "2.4.6",
|
||||||
"@biomejs/cli-linux-x64": "2.4.2",
|
"@biomejs/cli-linux-x64": "2.4.6",
|
||||||
"@biomejs/cli-linux-x64-musl": "2.4.2",
|
"@biomejs/cli-linux-x64-musl": "2.4.6",
|
||||||
"@biomejs/cli-win32-arm64": "2.4.2",
|
"@biomejs/cli-win32-arm64": "2.4.6",
|
||||||
"@biomejs/cli-win32-x64": "2.4.2"
|
"@biomejs/cli-win32-x64": "2.4.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.6.tgz",
|
||||||
"integrity": "sha512-3pEcKCP/1POKyaZZhXcxFl3+d9njmeAihZ17k8lL/1vk+6e0Cbf0yPzKItFiT+5Yh6TQA4uKvnlqe0oVZwRxCA==",
|
"integrity": "sha512-NW18GSyxr+8sJIqgoGwVp5Zqm4SALH4b4gftIA0n62PTuBs6G2tHlwNAOj0Vq0KKSs7Sf88VjjmHh0O36EnzrQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -586,9 +583,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-darwin-x64": {
|
"node_modules/@biomejs/cli-darwin-x64": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.6.tgz",
|
||||||
"integrity": "sha512-P7hK1jLVny+0R9UwyGcECxO6sjETxfPyBm/1dmFjnDOHgdDPjPqozByunrwh4xPKld8sxOr5eAsSqal5uKgeBg==",
|
"integrity": "sha512-4uiE/9tuI7cnjtY9b07RgS7gGyYOAfIAGeVJWEfeCnAarOAS7qVmuRyX6d7JTKw28/mt+rUzMasYeZ+0R/U1Mw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -603,9 +600,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-linux-arm64": {
|
"node_modules/@biomejs/cli-linux-arm64": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.6.tgz",
|
||||||
"integrity": "sha512-DI3Mi7GT2zYNgUTDEbSjl3e1KhoP76OjQdm8JpvZYZWtVDRyLd3w8llSr2TWk1z+U3P44kUBWY3X7H9MD1/DGQ==",
|
"integrity": "sha512-kMLaI7OF5GN1Q8Doymjro1P8rVEoy7BKQALNz6fiR8IC1WKduoNyteBtJlHT7ASIL0Cx2jR6VUOBIbcB1B8pew==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -620,9 +617,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.6.tgz",
|
||||||
"integrity": "sha512-/x04YK9+7erw6tYEcJv9WXoBHcULI/wMOvNdAyE9S3JStZZ9yJyV67sWAI+90UHuDo/BDhq0d96LDqGlSVv7WA==",
|
"integrity": "sha512-F/JdB7eN22txiTqHM5KhIVt0jVkzZwVYrdTR1O3Y4auBOQcXxHK4dxULf4z43QyZI5tsnQJrRBHZy7wwtL+B3A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -637,9 +634,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-linux-x64": {
|
"node_modules/@biomejs/cli-linux-x64": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.6.tgz",
|
||||||
"integrity": "sha512-GK2ErnrKpWFigYP68cXiCHK4RTL4IUWhK92AFS3U28X/nuAL5+hTuy6hyobc8JZRSt+upXt1nXChK+tuHHx4mA==",
|
"integrity": "sha512-oHXmUFEoH8Lql1xfc3QkFLiC1hGR7qedv5eKNlC185or+o4/4HiaU7vYODAH3peRCfsuLr1g6v2fK9dFFOYdyw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -654,9 +651,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.6.tgz",
|
||||||
"integrity": "sha512-wbBmTkeAoAYbOQ33f6sfKG7pcRSydQiF+dTYOBjJsnXO2mWEOQHllKlC2YVnedqZFERp2WZhFUoO7TNRwnwEHQ==",
|
"integrity": "sha512-C9s98IPDu7DYarjlZNuzJKTjVHN03RUnmHV5htvqsx6vEUXCDSJ59DNwjKVD5XYoSS4N+BYhq3RTBAL8X6svEg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -671,9 +668,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-win32-arm64": {
|
"node_modules/@biomejs/cli-win32-arm64": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.6.tgz",
|
||||||
"integrity": "sha512-k2uqwLYrNNxnaoiW3RJxoMGnbKda8FuCmtYG3cOtVljs3CzWxaTR+AoXwKGHscC9thax9R4kOrtWqWN0+KdPTw==",
|
"integrity": "sha512-xzThn87Pf3YrOGTEODFGONmqXpTwUNxovQb72iaUOdcw8sBSY3+3WD8Hm9IhMYLnPi0n32s3L3NWU6+eSjfqFg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -688,9 +685,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@biomejs/cli-win32-x64": {
|
"node_modules/@biomejs/cli-win32-x64": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.6.tgz",
|
||||||
"integrity": "sha512-9ma7C4g8Sq3cBlRJD2yrsHXB1mnnEBdpy7PhvFrylQWQb4PoyCmPucdX7frvsSBQuFtIiKCrolPl/8tCZrKvgQ==",
|
"integrity": "sha512-7++XhnsPlr1HDbor5amovPjOH6vsrFOCdp93iKXhFn6bcMUI6soodj3WWKfgEO6JosKU1W5n3uky3WW9RlRjTg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -5879,55 +5876,6 @@
|
||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@testing-library/dom": {
|
|
||||||
"version": "10.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
|
||||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/code-frame": "^7.10.4",
|
|
||||||
"@babel/runtime": "^7.12.5",
|
|
||||||
"@types/aria-query": "^5.0.1",
|
|
||||||
"aria-query": "5.3.0",
|
|
||||||
"dom-accessibility-api": "^0.5.9",
|
|
||||||
"lz-string": "^1.5.0",
|
|
||||||
"picocolors": "1.1.1",
|
|
||||||
"pretty-format": "^27.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@testing-library/react": {
|
|
||||||
"version": "16.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
|
|
||||||
"integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.12.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@testing-library/dom": "^10.0.0",
|
|
||||||
"@types/react": "^18.0.0 || ^19.0.0",
|
|
||||||
"@types/react-dom": "^18.0.0 || ^19.0.0",
|
|
||||||
"react": "^18.0.0 || ^19.0.0",
|
|
||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tiptap/core": {
|
"node_modules/@tiptap/core": {
|
||||||
"version": "3.20.0",
|
"version": "3.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.0.tgz",
|
||||||
|
|
@ -6461,13 +6409,6 @@
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/aria-query": {
|
|
||||||
"version": "5.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
|
||||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
|
|
@ -6969,16 +6910,6 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aria-query": {
|
|
||||||
"version": "5.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
|
||||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"dequal": "^2.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/array-buffer-byte-length": {
|
"node_modules/array-buffer-byte-length": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
|
||||||
|
|
@ -8194,13 +8125,6 @@
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/dom-accessibility-api": {
|
|
||||||
"version": "0.5.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
|
||||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/dom-helpers": {
|
"node_modules/dom-helpers": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||||
|
|
@ -10962,16 +10886,6 @@
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lz-string": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"lz-string": "bin/bin.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
|
|
@ -12407,41 +12321,6 @@
|
||||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pretty-format": {
|
|
||||||
"version": "27.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
|
||||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": "^5.0.1",
|
|
||||||
"ansi-styles": "^5.0.0",
|
|
||||||
"react-is": "^17.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pretty-format/node_modules/ansi-styles": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pretty-format/node_modules/react-is": {
|
|
||||||
"version": "17.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/prism-react-renderer": {
|
"node_modules/prism-react-renderer": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz",
|
||||||
|
|
@ -13478,15 +13357,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-to-print": {
|
|
||||||
"version": "3.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-3.2.0.tgz",
|
|
||||||
"integrity": "sha512-IX2D0mebKMgYTBD6s5tf9B7YRL3RFWjRoevYK8JKgRwn94Rep7PFZyeOTGjCmXofKB1SKzvPSzDrAMG4I2PIwg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ~19"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-transition-group": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,6 @@
|
||||||
"react-phone-number-input": "^3.4.14",
|
"react-phone-number-input": "^3.4.14",
|
||||||
"react-reverse-portal": "^2.3.0",
|
"react-reverse-portal": "^2.3.0",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"react-to-print": "^3.2.0",
|
|
||||||
"react-use": "^17.6.0",
|
"react-use": "^17.6.0",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"stripe": "^20.4.0",
|
"stripe": "^20.4.0",
|
||||||
|
|
@ -109,11 +108,9 @@
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.4.2",
|
"@biomejs/biome": "^2.4.6",
|
||||||
"@hookform/devtools": "^4.4.0",
|
"@hookform/devtools": "^4.4.0",
|
||||||
"@react-email/preview-server": "^5.2.8",
|
"@react-email/preview-server": "^5.2.8",
|
||||||
"@testing-library/dom": "^10.4.1",
|
|
||||||
"@testing-library/react": "^16.3.2",
|
|
||||||
"@total-typescript/ts-reset": "^0.6.1",
|
"@total-typescript/ts-reset": "^0.6.1",
|
||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"@types/node": "^24.2.0",
|
"@types/node": "^24.2.0",
|
||||||
|
|
@ -139,4 +136,4 @@
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "7.9.0"
|
"initVersion": "7.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { it } from "date-fns/locale";
|
import { it } from "date-fns/locale";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRef } from "react";
|
import toast from "react-hot-toast";
|
||||||
import { useReactToPrint } from "react-to-print";
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Card } from "~/components/ui/card";
|
import { Card } from "~/components/ui/card";
|
||||||
import { PaymentMethodToString, type PaymentType } from "~/i18n/stripe";
|
import { PaymentMethodToString, type PaymentType } from "~/i18n/stripe";
|
||||||
import { formatCurrency } from "~/lib/utils";
|
import { formatCurrency } from "~/lib/utils";
|
||||||
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export function ReceiptGenerator({
|
export function ReceiptGenerator({
|
||||||
data,
|
data,
|
||||||
|
|
@ -17,11 +18,36 @@ export function ReceiptGenerator({
|
||||||
data: PurchaseData;
|
data: PurchaseData;
|
||||||
raw: boolean;
|
raw: boolean;
|
||||||
}) {
|
}) {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const { mutate: generatePdf } =
|
||||||
const reactToPrintFn = useReactToPrint({
|
api.pagamenti.getRicevutaCortesiaPdf.useMutation({
|
||||||
contentRef,
|
onMutate: () => {
|
||||||
//print: async (target) => {console.log("Printing...", target.contentDocument);},
|
toast.loading("Generazione PDF in corso...", {
|
||||||
});
|
id: "generate-pdf",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: (res) => {
|
||||||
|
toast.success("PDF generato con successo!", {
|
||||||
|
id: "generate-pdf",
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([res as unknown as ArrayBuffer], {
|
||||||
|
type: "application/pdf",
|
||||||
|
});
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = `ricevuta-cortesia-${format(data.date, "yyyy-MM-dd")}.pdf`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(`Errore durante la generazione del PDF: ${error.message}`, {
|
||||||
|
id: "generate-pdf",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
if (raw) {
|
if (raw) {
|
||||||
return <Receipt data={data} />;
|
return <Receipt data={data} />;
|
||||||
}
|
}
|
||||||
|
|
@ -31,12 +57,12 @@ export function ReceiptGenerator({
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h1 className="font-bold text-2xl">Ricevuta di cortesia</h1>
|
<h1 className="font-bold text-2xl">Ricevuta di cortesia</h1>
|
||||||
|
|
||||||
<Button className="print:hidden" onClick={() => reactToPrintFn()}>
|
<Button onClick={() => generatePdf({ ordineId: data.ordineId })}>
|
||||||
Stampa
|
Stampa
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div ref={contentRef}>
|
<div>
|
||||||
<Card className="p-4 print:border-none print:shadow-none">
|
<Card className="p-4">
|
||||||
<Receipt data={data} />
|
<Receipt data={data} />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -52,7 +78,7 @@ interface Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PurchaseData {
|
export interface PurchaseData {
|
||||||
paymentId: string;
|
ordineId: OrdiniOrdineId;
|
||||||
date: Date;
|
date: Date;
|
||||||
|
|
||||||
clienteNome: string;
|
clienteNome: string;
|
||||||
|
|
@ -102,8 +128,8 @@ function Receipt({ data }: ReceiptProps) {
|
||||||
</h2>
|
</h2>
|
||||||
<div className="mt-2 flex justify-between">
|
<div className="mt-2 flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">ID Pagamento</p>
|
<p className="text-muted-foreground text-sm">ID Ordine</p>
|
||||||
<p className="text-sm">{data.paymentId}</p>
|
<p className="text-sm">{data.ordineId}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-muted-foreground text-sm">Data</p>
|
<p className="text-muted-foreground text-sm">Data</p>
|
||||||
|
|
|
||||||
|
|
@ -192,23 +192,13 @@ export const InteressatoButtonServizio = ({
|
||||||
}: {
|
}: {
|
||||||
annuncioId: AnnunciId;
|
annuncioId: AnnunciId;
|
||||||
}) => {
|
}) => {
|
||||||
const { isAdmin, servizioId, userId } = useServizio();
|
const { isAdmin, servizioId } = useServizio();
|
||||||
const { locale } = useTranslation();
|
const { locale } = useTranslation();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const invalidateAll = async () => {
|
const invalidateAll = async () => {
|
||||||
await utils.intrests.getUserInterests.invalidate();
|
await utils.intrests.getUserInterests.invalidate();
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getServizioData.invalidate({servizioId});
|
|
||||||
await utils.servizio.getAnnunciAvailableToAdd.invalidate({
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
await utils.servizio.AnnuncioServizioTipologiaMatch.invalidate({
|
|
||||||
userId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
const { mutate: adminAdd, isPending: isAdminPending } =
|
const { mutate: adminAdd, isPending: isAdminPending } =
|
||||||
api.servizio.addServizioAnnunci.useMutation({
|
api.servizio.addServizioAnnunci.useMutation({
|
||||||
|
|
|
||||||
|
|
@ -492,7 +492,10 @@ export const CarouselAnnuncio = ({
|
||||||
media: "image",
|
media: "image",
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
key={`videoplayer-${idx}-${openModal}`}
|
key={`videoplayer-${
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: <ok>
|
||||||
|
idx
|
||||||
|
}-${openModal}`}
|
||||||
videoSrc={getStorageUrl({
|
videoSrc={getStorageUrl({
|
||||||
storageId: video.video,
|
storageId: video.video,
|
||||||
params: {
|
params: {
|
||||||
|
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
import { FormNewOrder } from "~/forms/FormNewOrdine";
|
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import {
|
|
||||||
Credenza,
|
|
||||||
CredenzaBody,
|
|
||||||
CredenzaClose,
|
|
||||||
CredenzaContent,
|
|
||||||
CredenzaDescription,
|
|
||||||
CredenzaFooter,
|
|
||||||
CredenzaHeader,
|
|
||||||
CredenzaTitle,
|
|
||||||
CredenzaTrigger,
|
|
||||||
} from "../custom_ui/credenza";
|
|
||||||
import { LoadingPage } from "../loading";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
|
|
||||||
export const OrdineManualeDialog = ({
|
|
||||||
userId,
|
|
||||||
servizioId,
|
|
||||||
}: {
|
|
||||||
userId: UsersId;
|
|
||||||
servizioId: ServizioServizioId;
|
|
||||||
}) => {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
return (
|
|
||||||
<Credenza onOpenChange={setOpen} open={open}>
|
|
||||||
<CredenzaTrigger asChild>
|
|
||||||
<Button size="sm" variant="outline">
|
|
||||||
Nuovo Ordine Manuale
|
|
||||||
</Button>
|
|
||||||
</CredenzaTrigger>
|
|
||||||
<CredenzaContent className="max-h-[90vh] w-full px-3 sm:max-w-5xl">
|
|
||||||
<CredenzaHeader>
|
|
||||||
<CredenzaTitle>Nuovo Ordine Manuale</CredenzaTitle>
|
|
||||||
<CredenzaDescription className="sr-only">
|
|
||||||
nuovo ordine modal
|
|
||||||
</CredenzaDescription>
|
|
||||||
</CredenzaHeader>
|
|
||||||
<Content servizioId={servizioId} setOpen={setOpen} userId={userId} />
|
|
||||||
</CredenzaContent>
|
|
||||||
</Credenza>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Content = ({
|
|
||||||
userId,
|
|
||||||
servizioId,
|
|
||||||
setOpen,
|
|
||||||
}: {
|
|
||||||
userId: UsersId;
|
|
||||||
servizioId: ServizioServizioId;
|
|
||||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}) => {
|
|
||||||
const { data, isLoading } = api.prezziario.getPrezziario.useQuery();
|
|
||||||
|
|
||||||
if (isLoading) return <LoadingPage />;
|
|
||||||
|
|
||||||
if (!data)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<CredenzaBody className="max-h-[80vh] w-full overflow-y-auto pb-5">
|
|
||||||
<p className="text-center text-muted-foreground text-sm">
|
|
||||||
Errore nel caricamento del prezziario.
|
|
||||||
</p>
|
|
||||||
</CredenzaBody>
|
|
||||||
|
|
||||||
<CredenzaFooter>
|
|
||||||
<CredenzaClose asChild>
|
|
||||||
<Button aria-label="Close Credenza" variant="outline">
|
|
||||||
Chiudi
|
|
||||||
</Button>
|
|
||||||
</CredenzaClose>
|
|
||||||
</CredenzaFooter>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormNewOrder
|
|
||||||
prezziario={data}
|
|
||||||
servizioId={servizioId}
|
|
||||||
setOpen={setOpen}
|
|
||||||
userId={userId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { CircleCheck, CircleSlash, ReceiptText, Trash2 } from "lucide-react";
|
import { CircleCheck, CircleSlash, ReceiptText } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import toast from "react-hot-toast";
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
import { Confirm } from "~/components/confirm";
|
|
||||||
import {
|
import {
|
||||||
Credenza,
|
Credenza,
|
||||||
CredenzaBody,
|
CredenzaBody,
|
||||||
|
|
@ -12,87 +11,93 @@ import {
|
||||||
CredenzaHeader,
|
CredenzaHeader,
|
||||||
CredenzaTitle,
|
CredenzaTitle,
|
||||||
} from "~/components/custom_ui/credenza";
|
} from "~/components/custom_ui/credenza";
|
||||||
import { PaymentsTable } from "~/components/tables/payments-table";
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import {
|
import { FormEditOrder } from "~/forms/FormEditOrdine";
|
||||||
DropdownMenu,
|
import { formatCurrency } from "~/lib/utils";
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "~/components/ui/dropdown-menu";
|
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
|
||||||
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { Prezziario } from "~/schemas/public/Prezziario";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export const OrdiniModal = ({
|
export const AdminWrapperOrdiniModal = ({
|
||||||
isAdmin,
|
|
||||||
open,
|
|
||||||
setModalOpen,
|
|
||||||
ordineId,
|
ordineId,
|
||||||
userId,
|
setSelected,
|
||||||
}: {
|
}: {
|
||||||
isAdmin: boolean;
|
ordineId: OrdiniOrdineId | null;
|
||||||
open: boolean;
|
setSelected: Dispatch<SetStateAction<OrdiniOrdineId | null>>;
|
||||||
setModalOpen: (open: boolean) => void;
|
|
||||||
ordineId: OrdiniOrdineId;
|
|
||||||
userId: UsersId | undefined;
|
|
||||||
}) => {
|
}) => {
|
||||||
const utils = api.useUtils();
|
const { data: prezzi, isLoading } = api.prezziario.getPrezziario.useQuery();
|
||||||
const { mutate: remove } = api.servizio.removeOrder.useMutation({
|
if (isLoading || !ordineId || !prezzi) return null;
|
||||||
onError: (error) => {
|
return (
|
||||||
toast.error(`Errore durante la rimozione ordine: ${error.message}`);
|
<AdminOrdiniModal
|
||||||
},
|
ordineId={ordineId}
|
||||||
onSuccess: async () => {
|
prezzi={prezzi}
|
||||||
toast.success("Ordine rimosso con successo!");
|
setSelected={setSelected}
|
||||||
await utils.servizio.getOrdini.invalidate({
|
/>
|
||||||
userId,
|
);
|
||||||
});
|
};
|
||||||
setModalOpen(false);
|
const AdminOrdiniModal = ({
|
||||||
},
|
prezzi,
|
||||||
});
|
setSelected,
|
||||||
const { mutate: update } = api.servizio.updateOrder.useMutation({
|
ordineId,
|
||||||
onError: (error) => {
|
}: {
|
||||||
toast.error(`Errore durante la modifica: ${error.message}`);
|
prezzi: Prezziario[];
|
||||||
},
|
setSelected: Dispatch<SetStateAction<OrdiniOrdineId | null>>;
|
||||||
onSuccess: async (data) => {
|
ordineId: OrdiniOrdineId;
|
||||||
if (data.type === OrderTypeEnum.Acconto && data.isActive) {
|
}) => {
|
||||||
updateServizio({
|
const { data } = api.servizio.getOrdineById.useQuery({
|
||||||
data: {
|
ordineId,
|
||||||
decorrenza: new Date(),
|
|
||||||
isOkAcconto: true,
|
|
||||||
},
|
|
||||||
servizioId: data.servizio_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
toast.success("Ordine aggiornato con successo!");
|
|
||||||
await utils.servizio.getOrdini.invalidate({
|
|
||||||
userId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getOrdineById.invalidate({
|
|
||||||
ordineId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
|
if (!data) return null;
|
||||||
onError: (error) => {
|
|
||||||
toast.error(error.message);
|
|
||||||
},
|
|
||||||
onSuccess: async (data) => {
|
|
||||||
toast.success("Servizio modificato con successo");
|
|
||||||
await utils.servizio.getUserServizi.invalidate();
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({
|
|
||||||
servizioId: data.servizio_id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Credenza
|
||||||
|
onOpenChange={(b) => {
|
||||||
|
if (!b) setSelected(null);
|
||||||
|
}}
|
||||||
|
open={ordineId !== null}
|
||||||
|
>
|
||||||
|
<CredenzaContent className="max-h-[90vh] max-w-2xl sm:max-w-4xl">
|
||||||
|
<CredenzaHeader>
|
||||||
|
<CredenzaTitle>Dettagli Ordine</CredenzaTitle>
|
||||||
|
<CredenzaDescription>
|
||||||
|
Visualizza i dettagli e i pagamenti associati.
|
||||||
|
</CredenzaDescription>
|
||||||
|
</CredenzaHeader>
|
||||||
|
<CredenzaBody className="max-h-[80vh] space-y-2 overflow-auto pb-5">
|
||||||
|
<p>Id Ordine: {data.ordine_id}</p>
|
||||||
|
{data.isActive &&
|
||||||
|
data.paymentstatus === PaymentStatusEnum.success && (
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
href={`/servizio/ricevuta-cortesia/${data.ordine_id}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<Button variant="outline">
|
||||||
|
Ricevuta di cortesia <ReceiptText />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<FormEditOrder
|
||||||
|
close={() => setSelected(null)}
|
||||||
|
data={data}
|
||||||
|
prezziario={prezzi}
|
||||||
|
/>
|
||||||
|
</CredenzaBody>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserOrdiniModal = ({
|
||||||
|
setSelected,
|
||||||
|
ordineId,
|
||||||
|
}: {
|
||||||
|
setSelected: (ordineId: OrdiniOrdineId | null) => void;
|
||||||
|
ordineId: OrdiniOrdineId;
|
||||||
|
}) => {
|
||||||
const { data } = api.servizio.getOrdineById.useQuery({
|
const { data } = api.servizio.getOrdineById.useQuery({
|
||||||
ordineId,
|
ordineId,
|
||||||
});
|
});
|
||||||
|
|
@ -101,9 +106,9 @@ export const OrdiniModal = ({
|
||||||
return (
|
return (
|
||||||
<Credenza
|
<Credenza
|
||||||
onOpenChange={(b) => {
|
onOpenChange={(b) => {
|
||||||
setModalOpen(b);
|
if (!b) setSelected(null);
|
||||||
}}
|
}}
|
||||||
open={open}
|
open={ordineId !== null}
|
||||||
>
|
>
|
||||||
<CredenzaContent className="max-h-[90vh] max-w-2xl sm:max-w-4xl">
|
<CredenzaContent className="max-h-[90vh] max-w-2xl sm:max-w-4xl">
|
||||||
<CredenzaHeader>
|
<CredenzaHeader>
|
||||||
|
|
@ -115,12 +120,7 @@ export const OrdiniModal = ({
|
||||||
<CredenzaBody className="max-h-[80vh] space-y-2 overflow-auto pb-5">
|
<CredenzaBody className="max-h-[80vh] space-y-2 overflow-auto pb-5">
|
||||||
<p>Id Ordine: {data.ordine_id}</p>
|
<p>Id Ordine: {data.ordine_id}</p>
|
||||||
<p>
|
<p>
|
||||||
Data Ordine:
|
Data Ordine: {new Date(data.created_at).toLocaleDateString("it-IT")}
|
||||||
{new Date(data.created_at).toLocaleDateString("it-IT", {
|
|
||||||
day: "2-digit",
|
|
||||||
month: "2-digit",
|
|
||||||
year: "numeric",
|
|
||||||
})}
|
|
||||||
</p>
|
</p>
|
||||||
<p>Tipologia: {data.type.toString()}</p>
|
<p>Tipologia: {data.type.toString()}</p>
|
||||||
<p>Pack ID: {data.packid}</p>
|
<p>Pack ID: {data.packid}</p>
|
||||||
|
|
@ -138,11 +138,12 @@ export const OrdiniModal = ({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Importo: {formatCurrency(data.amount_cent / 100)}{" "}
|
||||||
|
{data.sconto > 0 && <span>({data.sconto}%)</span>}
|
||||||
|
</p>
|
||||||
{data.isActive &&
|
{data.isActive &&
|
||||||
data.pagamenti.find(
|
data.paymentstatus === PaymentStatusEnum.success && (
|
||||||
(p) => p.paymentstatus === PaymentStatusEnum.success,
|
|
||||||
) && (
|
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
href={`/servizio/ricevuta-cortesia/${data.ordine_id}`}
|
href={`/servizio/ricevuta-cortesia/${data.ordine_id}`}
|
||||||
|
|
@ -154,59 +155,8 @@ export const OrdiniModal = ({
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PaymentsTable data={data.pagamenti} isAdmin={isAdmin} isUserView />
|
|
||||||
</CredenzaBody>
|
</CredenzaBody>
|
||||||
<CredenzaFooter
|
<CredenzaFooter className="flex w-full sm:justify-end">
|
||||||
className={cn(
|
|
||||||
"flex w-full",
|
|
||||||
isAdmin ? "sm:justify-between" : "sm:justify-end",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{isAdmin && (
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button aria-label="Modifica Status" variant="outline">
|
|
||||||
Modifica Status
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent>
|
|
||||||
<DropdownMenuLabel>Status</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
onValueChange={(v) => {
|
|
||||||
update({
|
|
||||||
data: { isActive: v === "true" },
|
|
||||||
ordineId,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
value={data.isActive ? "true" : "false"}
|
|
||||||
>
|
|
||||||
<DropdownMenuRadioItem value="true">
|
|
||||||
Attivo
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="false">
|
|
||||||
Non Attivo
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<Confirm
|
|
||||||
description="Sei sicuro di voler eliminare l'ordine?"
|
|
||||||
onConfirm={() => {
|
|
||||||
remove({ ordineId });
|
|
||||||
}}
|
|
||||||
title="Elimina ordine"
|
|
||||||
>
|
|
||||||
<Button aria-label="Elimina Ordine" variant="destructive">
|
|
||||||
<Trash2 className="size-6" />
|
|
||||||
Elimina Ordine
|
|
||||||
</Button>
|
|
||||||
</Confirm>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<CredenzaClose asChild>
|
<CredenzaClose asChild>
|
||||||
<Button variant="ghost">Chiudi</Button>
|
<Button variant="ghost">Chiudi</Button>
|
||||||
</CredenzaClose>
|
</CredenzaClose>
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,10 @@ export const ChatAttachments = ({
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className="flex flex-row items-center justify-between border-b py-1"
|
className="flex flex-row items-center justify-between border-b py-1"
|
||||||
key={`${file.originalName}-${idx}`}
|
key={`${file.originalName}-${
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: <ok>
|
||||||
|
idx
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
aria-label="Visualizza Allegato"
|
aria-label="Visualizza Allegato"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TriangleAlertIcon } from "lucide-react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
|
|
@ -10,8 +11,6 @@ import {
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "~/components/ui/alert-dialog";
|
} from "~/components/ui/alert-dialog";
|
||||||
import { buttonVariants } from "~/components/ui/button";
|
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
export const Confirm = ({
|
export const Confirm = ({
|
||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
|
|
@ -42,18 +41,20 @@ export const Confirm = ({
|
||||||
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||||
)}
|
)}
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader className="place-items-center! items-center">
|
||||||
|
<div className="mx-auto mb-2 flex size-12 items-center justify-center rounded-full bg-destructive/10">
|
||||||
|
<TriangleAlertIcon className="size-6 text-destructive" />
|
||||||
|
</div>
|
||||||
<AlertDialogTitle>{title}</AlertDialogTitle>
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>{description}</AlertDialogDescription>
|
<AlertDialogDescription className="text-center">
|
||||||
|
{description}
|
||||||
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel onClick={() => onCancel?.()}>
|
<AlertDialogCancel onClick={() => onCancel?.()}>
|
||||||
{cancelText || "Annulla"}
|
{cancelText || "Annulla"}
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction onClick={() => onConfirm()} variant="destructive">
|
||||||
className={cn(buttonVariants({ variant: "destructive" }))}
|
|
||||||
onClick={() => onConfirm()}
|
|
||||||
>
|
|
||||||
{confirmText || "Conferma"}
|
{confirmText || "Conferma"}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export function DataTableViewOptions<TData>({
|
||||||
Filtro
|
Filtro
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[150px]">
|
<DropdownMenuContent align="end" className="w-37.5">
|
||||||
<DropdownMenuLabel>Filtra colonne</DropdownMenuLabel>
|
<DropdownMenuLabel>Filtra colonne</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{hidableColumns.map((column) => {
|
{hidableColumns.map((column) => {
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,10 @@ export const PDFViewer = ({ url }: { url: string }) => {
|
||||||
>
|
>
|
||||||
{Array.from(new Array(numPages), (_el, index) => (
|
{Array.from(new Array(numPages), (_el, index) => (
|
||||||
<Page
|
<Page
|
||||||
key={`page_${index + 1}`}
|
key={`page_${
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: <ok>
|
||||||
|
index + 1
|
||||||
|
}`}
|
||||||
pageNumber={index + 1}
|
pageNumber={index + 1}
|
||||||
width={width}
|
width={width}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ import {
|
||||||
} from "../ui/card";
|
} from "../ui/card";
|
||||||
import { Label } from "../ui/label";
|
import { Label } from "../ui/label";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
import { Switch } from "../ui/switch";
|
|
||||||
import { UploadModal } from "../upload_modal";
|
import { UploadModal } from "../upload_modal";
|
||||||
import { ServizioPacksInfos } from "./servizio_actions";
|
import { ServizioPacksInfos } from "./servizio_actions";
|
||||||
import { DocCombo, DocInfo } from "./shared";
|
import { DocCombo, DocInfo } from "./shared";
|
||||||
|
|
@ -106,7 +105,7 @@ export const AdminLavoraConferma = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const DebugActions = () => {
|
const DebugActions = () => {
|
||||||
const { servizioId, userId } = useServizio();
|
const { servizioId } = useServizio();
|
||||||
const { annuncioId } = useServizioAnnuncio();
|
const { annuncioId } = useServizioAnnuncio();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate } = api.servizio.updateServizioAnnunci.useMutation({
|
const { mutate } = api.servizio.updateServizioAnnunci.useMutation({
|
||||||
|
|
@ -114,10 +113,7 @@ const DebugActions = () => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
await utils.servizio.invalidate();
|
||||||
userId: userId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
|
|
||||||
toast.success("Dati salvati con successo");
|
toast.success("Dati salvati con successo");
|
||||||
},
|
},
|
||||||
|
|
@ -166,6 +162,10 @@ const DebugActions = () => {
|
||||||
const DocSection = () => {
|
const DocSection = () => {
|
||||||
const { userId } = useServizio();
|
const { userId } = useServizio();
|
||||||
const { data, updateServizioAnnunci } = useServizioAnnuncio();
|
const { data, updateServizioAnnunci } = useServizioAnnuncio();
|
||||||
|
const initialDoc = data.doc_conferma_ref;
|
||||||
|
const [selectedDoc, setSelectedDoc] = useState<string | null>(
|
||||||
|
data.doc_conferma_ref,
|
||||||
|
);
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
|
@ -181,6 +181,18 @@ const DocSection = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
await updateServizioAnnunci({
|
||||||
|
doc_conferma_added: selectedDoc != null,
|
||||||
|
doc_conferma_ref: selectedDoc,
|
||||||
|
});
|
||||||
|
if (selectedDoc) {
|
||||||
|
setUserStorage({
|
||||||
|
storageId: selectedDoc,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
if (isLoading) return <LoadingPage />;
|
if (isLoading) return <LoadingPage />;
|
||||||
if (files === undefined) {
|
if (files === undefined) {
|
||||||
return <p>Errore nel caricamento dei documenti</p>;
|
return <p>Errore nel caricamento dei documenti</p>;
|
||||||
|
|
@ -194,55 +206,69 @@ const DocSection = () => {
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{data.doc_conferma_ref && (
|
{selectedDoc && (
|
||||||
<div className="flex flex-wrap items-start gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<DocInfo storageId={data.doc_conferma_ref}>
|
<DocInfo storageId={selectedDoc}>
|
||||||
<Button
|
<Button
|
||||||
aria-label="Reset Conferma"
|
aria-label="Reset Contratto"
|
||||||
className="flex items-center gap-3"
|
className="flex items-center gap-3"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await updateServizioAnnunci({
|
await updateServizioAnnunci({
|
||||||
doc_conferma_added: false,
|
doc_conferma_added: false,
|
||||||
doc_conferma_ref: null,
|
doc_conferma_ref: null,
|
||||||
});
|
});
|
||||||
|
setSelectedDoc(null);
|
||||||
}}
|
}}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
>
|
>
|
||||||
<Trash2 />
|
<Trash2 className="size-6" />
|
||||||
</Button>
|
</Button>
|
||||||
</DocInfo>
|
</DocInfo>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<DocCombo
|
||||||
<DocCombo
|
current={selectedDoc}
|
||||||
current={data.doc_conferma_ref}
|
files={files}
|
||||||
files={files}
|
onSelect={(f) => {
|
||||||
onSelect={async (f) => {
|
setSelectedDoc(f.id);
|
||||||
await updateServizioAnnunci({
|
}}
|
||||||
doc_conferma_added: true,
|
/>
|
||||||
doc_conferma_ref: f.id,
|
<UploadModal
|
||||||
});
|
cb_onUpload={({ storageIds }) => {
|
||||||
|
if (storageIds.length === 1 && storageIds[0]) {
|
||||||
setUserStorage({
|
setSelectedDoc(storageIds[0]);
|
||||||
storageId: f.id,
|
}
|
||||||
userId,
|
}}
|
||||||
});
|
isAdmin
|
||||||
}}
|
userId={userId}
|
||||||
/>
|
/>
|
||||||
<UploadModal
|
|
||||||
cb_onUpload={async ({ storageIds }) => {
|
|
||||||
if (storageIds.length === 1 && storageIds[0]) {
|
|
||||||
await updateServizioAnnunci({
|
|
||||||
doc_conferma_added: true,
|
|
||||||
doc_conferma_ref: storageIds[0],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
isAdmin
|
|
||||||
userId={userId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
<CardFooter className="gap-4">
|
||||||
|
<Button
|
||||||
|
aria-label="Save Contratto"
|
||||||
|
disabled={selectedDoc === initialDoc}
|
||||||
|
onClick={async () => {
|
||||||
|
await handleSave();
|
||||||
|
}}
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
Salva
|
||||||
|
</Button>
|
||||||
|
{selectedDoc !== initialDoc ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<CircleAlert className="size-5 text-orange-500" />
|
||||||
|
<span className="text-orange-500">Salva modifiche</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
initialDoc &&
|
||||||
|
selectedDoc === initialDoc && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<CircleCheck className="size-5 text-green-500" />
|
||||||
|
<span className="text-green-500">Dati salvati</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -539,7 +565,7 @@ const TransitorioSection = () => {
|
||||||
const { servizio } = useServizio();
|
const { servizio } = useServizio();
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { servizioId, userId } = useServizio();
|
const { servizioId } = useServizio();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
@ -547,10 +573,7 @@ const TransitorioSection = () => {
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio modificato con successo");
|
toast.success("Servizio modificato con successo");
|
||||||
await utils.servizio.getUserServizi.invalidate();
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({ servizioId });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -604,7 +627,7 @@ const TransitorioSection = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const StabileSection = () => {
|
const StabileSection = () => {
|
||||||
const { servizio, servizioId, userId } = useServizio();
|
const { servizio, servizioId } = useServizio();
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
const [value, setValue] = useState(servizio.budget);
|
const [value, setValue] = useState(servizio.budget);
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
@ -614,10 +637,7 @@ const StabileSection = () => {
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio modificato con successo");
|
toast.success("Servizio modificato con successo");
|
||||||
await utils.servizio.getUserServizi.invalidate();
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({ servizioId });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -664,7 +684,7 @@ const GeneralSection = () => {
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { servizio, servizioId, userId } = useServizio();
|
const { servizio, servizioId } = useServizio();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
@ -672,10 +692,7 @@ const GeneralSection = () => {
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio modificato con successo");
|
toast.success("Servizio modificato con successo");
|
||||||
await utils.servizio.getUserServizi.invalidate();
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({ servizioId });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -795,11 +812,8 @@ const SendConferma = () => {
|
||||||
const { mutate: handleUpdate } =
|
const { mutate: handleUpdate } =
|
||||||
api.servizio.adminUpdateConfermaStatus.useMutation({
|
api.servizio.adminUpdateConfermaStatus.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate();
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getServizioData.invalidate({
|
|
||||||
servizioId: servizio.servizio_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
toast.success("Status aggiornato con successo");
|
toast.success("Status aggiornato con successo");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
@ -848,24 +862,28 @@ const SendConferma = () => {
|
||||||
completi
|
completi
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
<p className="font-semibold text-lg">
|
||||||
|
ATTENZIONE: Il saldo verrà aggiunto (se necessario), in base ai
|
||||||
|
parametri, all'invio della conferma
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="mt-4 flex items-center gap-3">
|
<div className="mt-4 flex items-center gap-3">
|
||||||
<Switch
|
<Button
|
||||||
aria-label="Conferma Lavorata"
|
aria-label="Send Conferma"
|
||||||
checked={data.hasConfermaAdmin}
|
disabled={!docOk || !caparraOk || data.hasConfermaAdmin}
|
||||||
disabled={!docOk || !caparraOk}
|
onClick={() => {
|
||||||
id="conferma"
|
|
||||||
onCheckedChange={(c) => {
|
|
||||||
handleUpdate({
|
handleUpdate({
|
||||||
servizioId: servizio.servizio_id,
|
servizioId: servizio.servizio_id,
|
||||||
annuncioId: data.id,
|
annuncioId: data.id,
|
||||||
status: c,
|
status: true,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
size="lg"
|
||||||
<Label className="text-base" htmlFor="conferma">
|
variant="orange"
|
||||||
Imposta Conferma come Lavorata
|
>
|
||||||
</Label>
|
<CircleAlert className="size-5" />
|
||||||
|
<span>Invia la conferma</span>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,7 @@ const ContrattoImport = () => {
|
||||||
<CardDescription className="sr-only">desc</CardDescription>
|
<CardDescription className="sr-only">desc</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
aria-label="Import Contratto"
|
aria-label="Import Contratto"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
|
@ -289,7 +289,7 @@ const ContrattoImport = () => {
|
||||||
variant="info"
|
variant="info"
|
||||||
>
|
>
|
||||||
<ClipboardPaste className="size-4" />
|
<ClipboardPaste className="size-4" />
|
||||||
Clicca qui per importare la stringa dal gestionale
|
Incolla la stringa dal gestionale
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
aria-label="Reset Contratto"
|
aria-label="Reset Contratto"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { Button, type ButtonConfigs } from "~/components/ui/button";
|
import { Button, type ButtonConfigs } from "~/components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
|
@ -24,6 +23,7 @@ import { cn } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { Confirm } from "../confirm";
|
import { Confirm } from "../confirm";
|
||||||
import { AdminLavoraConferma } from "./admin_conferma";
|
import { AdminLavoraConferma } from "./admin_conferma";
|
||||||
|
|
@ -32,7 +32,7 @@ import { ConfermaAnnuncioModal, ConfermaInLavorazioneModal } from "./conferma";
|
||||||
import { ContattiProprietarioModal } from "./modale_contatti";
|
import { ContattiProprietarioModal } from "./modale_contatti";
|
||||||
|
|
||||||
export const AdminActions = () => {
|
export const AdminActions = () => {
|
||||||
const { servizioId, userId, isAdmin } = useServizio();
|
const { servizioId, isAdmin } = useServizio();
|
||||||
const { data, annuncioId } = useServizioAnnuncio();
|
const { data, annuncioId } = useServizioAnnuncio();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
@ -42,11 +42,7 @@ export const AdminActions = () => {
|
||||||
toast.error("Errore durante la rimozione dell'annuncio");
|
toast.error("Errore durante la rimozione dell'annuncio");
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
toast.success("Annuncio rimosso");
|
toast.success("Annuncio rimosso");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -54,7 +50,7 @@ export const AdminActions = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full max-w-fit flex-wrap justify-start gap-3">
|
<div className="flex w-full max-w-fit flex-wrap justify-start gap-3">
|
||||||
{data.open_contatti_at != null && <AdminSendScheda />}
|
{<AdminSendScheda />}
|
||||||
{data.user_confirmed_at != null && <AdminLavoraConferma />}
|
{data.user_confirmed_at != null && <AdminLavoraConferma />}
|
||||||
{data.accettato_conferma_at != null && (
|
{data.accettato_conferma_at != null && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -93,7 +89,7 @@ export const AdminActions = () => {
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href={`/area-riservata/admin/scheda-annuncio-stampa/${data.id}`}
|
href={`/area-riservata/scheda-annuncio-stampa/${data.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Button size="sm" type="button" variant="outline">
|
<Button size="sm" type="button" variant="outline">
|
||||||
|
|
@ -160,7 +156,7 @@ const AdminSendScheda = () => {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<Mail />
|
<Mail />
|
||||||
<span>Re-Invia email contatti</span>
|
<span>Invia email contatti</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -277,7 +273,7 @@ const PostConfermaSection = () => {
|
||||||
}
|
}
|
||||||
if (!data.doc_contratto_added) {
|
if (!data.doc_contratto_added) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1 rounded-md border border-card bg-card p-4">
|
||||||
<h3 className="font-semibold text-green-500">
|
<h3 className="font-semibold text-green-500">
|
||||||
{locale === "it"
|
{locale === "it"
|
||||||
? "Ci siamo quasi! Stiamo preparando il contratto per te."
|
? "Ci siamo quasi! Stiamo preparando il contratto per te."
|
||||||
|
|
@ -303,16 +299,17 @@ const PostConfermaSection = () => {
|
||||||
? "Il contratto è pronto, puoi visualizzarlo e procedere al saldo:"
|
? "Il contratto è pronto, puoi visualizzarlo e procedere al saldo:"
|
||||||
: "The contract is ready, you can view it and proceed to payment:"}
|
: "The contract is ready, you can view it and proceed to payment:"}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex w-full flex-wrap gap-2">
|
||||||
{data.doc_contratto_ref ? (
|
{data.doc_contratto_ref ? (
|
||||||
<Link
|
<Link
|
||||||
aria-label={
|
aria-label={
|
||||||
locale === "it" ? "Visualizza contratto" : "View contract"
|
locale === "it" ? "Visualizza contratto" : "View contract"
|
||||||
}
|
}
|
||||||
|
className="w-full sm:w-fit"
|
||||||
href={`/servizio/contratto/${servizioId}/${annuncioId}`}
|
href={`/servizio/contratto/${servizioId}/${annuncioId}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Button className="w-full sm:w-fit">
|
<Button className="w-full">
|
||||||
<FileText />
|
<FileText />
|
||||||
{locale === "it"
|
{locale === "it"
|
||||||
? "Visualizza il contratto"
|
? "Visualizza il contratto"
|
||||||
|
|
@ -338,7 +335,11 @@ const PostConfermaSection = () => {
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!consulenzaPaid && data.contratto_tipo && <SaldoConsulenzaButton />}
|
{!consulenzaPaid &&
|
||||||
|
servizio.ordini.some(
|
||||||
|
(o) => o.type === OrderTypeEnum.Consulenza && !o.isActive,
|
||||||
|
) &&
|
||||||
|
data.contratto_tipo && <SaldoConsulenzaButton />}
|
||||||
|
|
||||||
{data.doc_registrazione_added &&
|
{data.doc_registrazione_added &&
|
||||||
(data.doc_registrazione_ref ? (
|
(data.doc_registrazione_ref ? (
|
||||||
|
|
@ -346,10 +347,11 @@ const PostConfermaSection = () => {
|
||||||
aria-label={
|
aria-label={
|
||||||
locale === "it" ? "Visualizza registrazione" : "View recording"
|
locale === "it" ? "Visualizza registrazione" : "View recording"
|
||||||
}
|
}
|
||||||
|
className="w-full sm:w-fit"
|
||||||
href={`/servizio/contratto-registrazione/${servizioId}/${annuncioId}`}
|
href={`/servizio/contratto-registrazione/${servizioId}/${annuncioId}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Button className="w-fit">
|
<Button className="w-full">
|
||||||
<FileBadge />
|
<FileBadge />
|
||||||
{locale === "it"
|
{locale === "it"
|
||||||
? "Visualizza la registrazione"
|
? "Visualizza la registrazione"
|
||||||
|
|
@ -380,7 +382,6 @@ const PostConfermaSection = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SaldoButton = ({
|
export const SaldoButton = ({
|
||||||
annuncioId,
|
|
||||||
variant = "info",
|
variant = "info",
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -390,40 +391,21 @@ export const SaldoButton = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { locale } = useTranslation();
|
const { locale } = useTranslation();
|
||||||
const { servizioId } = useServizio();
|
const { servizioId } = useServizio();
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { mutate } = api.servizio.setupSaldoServizio.useMutation({
|
|
||||||
onMutate: () => {
|
|
||||||
toast.loading(locale === "it" ? "Caricamento ..." : "Loading ...", {
|
|
||||||
id: "preparazioneSaldo",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSuccess: async (data) => {
|
|
||||||
toast.success(
|
|
||||||
locale === "it" ? "Pagamento saldo preparato" : "Payment prepared",
|
|
||||||
{
|
|
||||||
id: "preparazioneSaldo",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await router.push(`/servizio/pagamento/${data}`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Link
|
||||||
aria-label={locale === "it" ? "Procedi al saldo" : "Proceed to payment"}
|
className="w-full sm:w-fit"
|
||||||
className={cn("w-fit", className)}
|
href={`/servizio/pagamento/${servizioId}?type=${OrderTypeEnum.Saldo}`}
|
||||||
onClick={() => {
|
|
||||||
mutate({
|
|
||||||
annuncioId,
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
variant={variant}
|
|
||||||
>
|
>
|
||||||
<PackageCheck />
|
<Button
|
||||||
{locale === "it" ? "Procedi al saldo" : "Proceed to payment"}
|
aria-label={locale === "it" ? "Procedi al saldo" : "Proceed to payment"}
|
||||||
</Button>
|
className={cn("w-full", className)}
|
||||||
|
variant={variant}
|
||||||
|
>
|
||||||
|
<PackageCheck />
|
||||||
|
{locale === "it" ? "Procedi al saldo" : "Proceed to payment"}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -435,47 +417,27 @@ const SaldoConsulenzaButton = ({
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { locale } = useTranslation();
|
const { locale } = useTranslation();
|
||||||
const { annuncioId } = useServizioAnnuncio();
|
|
||||||
const { servizioId } = useServizio();
|
const { servizioId } = useServizio();
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { mutate } = api.servizio.setupSaldoConsulenzaServizio.useMutation({
|
|
||||||
onMutate: () => {
|
|
||||||
toast.loading(locale === "it" ? "Caricamento ..." : "Loading ...", {
|
|
||||||
id: "preparazioneSaldo",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSuccess: async (data) => {
|
|
||||||
toast.success(
|
|
||||||
locale === "it" ? "Pagamento saldo preparato" : "Payment prepared",
|
|
||||||
{
|
|
||||||
id: "preparazioneSaldo",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await router.push(`/servizio/pagamento/${data}`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Link
|
||||||
aria-label={
|
className="w-full sm:w-fit"
|
||||||
locale === "it"
|
href={`/servizio/pagamento/${servizioId}?type=${OrderTypeEnum.Consulenza}`}
|
||||||
? "Procedi al saldo consulenza"
|
|
||||||
: "Proceed to payment for the consultation"
|
|
||||||
}
|
|
||||||
className={cn("w-fit", className)}
|
|
||||||
onClick={() => {
|
|
||||||
mutate({
|
|
||||||
annuncioId,
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
variant={variant}
|
|
||||||
>
|
>
|
||||||
<PackageCheck />
|
<Button
|
||||||
{locale === "it"
|
aria-label={
|
||||||
? "Procedi al saldo della consulenza"
|
locale === "it"
|
||||||
: "Proceed to payment for the consultation"}
|
? "Procedi al saldo consulenza"
|
||||||
</Button>
|
: "Proceed to payment for the consultation"
|
||||||
|
}
|
||||||
|
className={cn("w-full", className)}
|
||||||
|
variant={variant}
|
||||||
|
>
|
||||||
|
<PackageCheck />
|
||||||
|
{locale === "it"
|
||||||
|
? "Procedi al saldo della consulenza"
|
||||||
|
: "Proceed to payment for the consultation"}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export const AnnuncioDisplay = ({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid @md:grid-cols-[12rem_1fr] grid-cols-[7rem_1fr] gap-x-0 gap-y-2 rounded-md bg-card md:max-w-3xl lg:gap-x-4 xl:gap-x-6",
|
"grid @md:grid-cols-[12rem_1fr] grid-cols-[7rem_1fr] @sm:gap-x-4 gap-x-1 gap-y-2 rounded-md bg-card md:max-w-3xl lg:gap-x-4 xl:gap-x-6",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export const ConfermaAnnuncioModal = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const InvioConferma = ({ closeModal }: { closeModal: () => void }) => {
|
const InvioConferma = ({ closeModal }: { closeModal: () => void }) => {
|
||||||
const { userId, servizioId } = useServizio();
|
const { servizioId } = useServizio();
|
||||||
const { annuncioId } = useServizioAnnuncio();
|
const { annuncioId } = useServizioAnnuncio();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
@ -107,8 +107,7 @@ const InvioConferma = ({ closeModal }: { closeModal: () => void }) => {
|
||||||
id: "updateRichiestaToast",
|
id: "updateRichiestaToast",
|
||||||
});
|
});
|
||||||
|
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
closeModal();
|
closeModal();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -117,7 +116,11 @@ const InvioConferma = ({ closeModal }: { closeModal: () => void }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CredenzaBody className="flex flex-col gap-4">
|
<CredenzaBody className="flex flex-col gap-4">
|
||||||
|
<p className="text-center font-semibold">
|
||||||
|
Hai fatto la visita? Se no contatta il proprietario!
|
||||||
|
</p>
|
||||||
<p>{t.richieste.conferma_txt}</p>
|
<p>{t.richieste.conferma_txt}</p>
|
||||||
|
|
||||||
<p className="text-sm">{t.richieste.conf_alert}</p>
|
<p className="text-sm">{t.richieste.conf_alert}</p>
|
||||||
</CredenzaBody>
|
</CredenzaBody>
|
||||||
<CredenzaFooter>
|
<CredenzaFooter>
|
||||||
|
|
@ -130,9 +133,9 @@ const InvioConferma = ({ closeModal }: { closeModal: () => void }) => {
|
||||||
servizioId,
|
servizioId,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
variant="success"
|
variant="info"
|
||||||
>
|
>
|
||||||
{t.conferma}
|
{t.procedi}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
|
|
||||||
<CredenzaClose asChild>
|
<CredenzaClose asChild>
|
||||||
|
|
@ -145,17 +148,11 @@ const InvioConferma = ({ closeModal }: { closeModal: () => void }) => {
|
||||||
|
|
||||||
const DocCheckpoint = () => {
|
const DocCheckpoint = () => {
|
||||||
const { t, locale } = useTranslation();
|
const { t, locale } = useTranslation();
|
||||||
const { userId, servizio, isAdmin, servizioId } = useServizio();
|
const { userId, servizio, isAdmin } = useServizio();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate, isPending } = api.servizio.updateServizio.useMutation({
|
const { mutate, isPending } = api.servizio.updateServizio.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
await utils.servizio.invalidate();
|
||||||
userId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
await utils.storage.retrieveUserFileData.invalidate({ userId });
|
|
||||||
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({ servizioId });
|
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
toast.error(`Errore aggiornamento servizio: ${err.message}`);
|
toast.error(`Errore aggiornamento servizio: ${err.message}`);
|
||||||
|
|
|
||||||
|
|
@ -87,12 +87,7 @@ const DocCheckpoint = () => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate, isPending } = api.users.editUserAnagrafica.useMutation({
|
const { mutate, isPending } = api.users.editUserAnagrafica.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
await utils.servizio.invalidate();
|
||||||
userId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getServizioData.invalidate({
|
|
||||||
servizioId: servizio.servizio_id,
|
|
||||||
});
|
|
||||||
await utils.storage.retrieveUserFileData.invalidate({ userId });
|
await utils.storage.retrieveUserFileData.invalidate({ userId });
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
|
|
@ -183,7 +178,7 @@ const DocCheckpoint = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const PreUnlock = () => {
|
const PreUnlock = () => {
|
||||||
const { userId, servizioId } = useServizio();
|
const { servizioId } = useServizio();
|
||||||
const { annuncioId } = useServizioAnnuncio();
|
const { annuncioId } = useServizioAnnuncio();
|
||||||
const { t, locale } = useTranslation();
|
const { t, locale } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -211,10 +206,7 @@ const PreUnlock = () => {
|
||||||
toast.success("Successo", {
|
toast.success("Successo", {
|
||||||
id: "unlock-contacts",
|
id: "unlock-contacts",
|
||||||
});
|
});
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
await utils.servizio.invalidate();
|
||||||
userId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,7 @@ const NewServizioModal = ({
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio creato con successo");
|
toast.success("Servizio creato con successo");
|
||||||
await utils.servizio.getUserServizi.invalidate();
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
|
|
||||||
setEditData(undefined);
|
setEditData(undefined);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|
@ -66,20 +65,9 @@ const NewServizioModal = ({
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
},
|
},
|
||||||
onSuccess: async (data) => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio modificato con successo");
|
toast.success("Servizio modificato con successo");
|
||||||
await utils.servizio.getUserServizi.invalidate();
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
await utils.servizio.getServizioData.invalidate({
|
|
||||||
servizioId: data.servizio_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
||||||
servizioId: data.servizio_id,
|
|
||||||
});
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({
|
|
||||||
servizioId: data.servizio_id,
|
|
||||||
});
|
|
||||||
setEditData(undefined);
|
setEditData(undefined);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import { add } from "date-fns";
|
import { add } from "date-fns";
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
|
BadgeCheck,
|
||||||
CalendarClock,
|
CalendarClock,
|
||||||
CircleCheck,
|
CircleCheck,
|
||||||
CircleCheckBig,
|
CircleCheckBig,
|
||||||
CircleMinus,
|
CircleMinus,
|
||||||
Clock,
|
Clock,
|
||||||
ClockFading,
|
ClockFading,
|
||||||
|
ExternalLink,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
PackageCheck,
|
PackageCheck,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
|
@ -20,11 +22,23 @@ import {
|
||||||
ServizioProvider,
|
ServizioProvider,
|
||||||
useServizio,
|
useServizio,
|
||||||
} from "~/providers/ServizioProvider";
|
} from "~/providers/ServizioProvider";
|
||||||
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { Confirm } from "../confirm";
|
import { Confirm } from "../confirm";
|
||||||
import { AlarmClockSVG } from "../svgs";
|
import { AlarmClockSVG } from "../svgs";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "../ui/alert-dialog";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -36,11 +50,7 @@ import {
|
||||||
import { SaldoButton } from "./annuncio_actions";
|
import { SaldoButton } from "./annuncio_actions";
|
||||||
import { AnnuncioCard, AnnuncioDisplay } from "./annuncio_card";
|
import { AnnuncioCard, AnnuncioDisplay } from "./annuncio_card";
|
||||||
import { AnnunciCompatibili } from "./compatibili_dialog";
|
import { AnnunciCompatibili } from "./compatibili_dialog";
|
||||||
import {
|
import { ServizioInfos } from "./servizio_actions";
|
||||||
AdminActions,
|
|
||||||
ServizioBasicActions,
|
|
||||||
ServizioPacksInfos,
|
|
||||||
} from "./servizio_actions";
|
|
||||||
|
|
||||||
export const ServizioList = ({
|
export const ServizioList = ({
|
||||||
servizi,
|
servizi,
|
||||||
|
|
@ -189,18 +199,18 @@ const Content = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dec_txt = servizio.decorrenza
|
const dec_txt = servizio.decorrenza
|
||||||
? new Date(servizio.decorrenza).toLocaleDateString("it-IT")
|
? new Date(servizio.decorrenza).toLocaleDateString("it-IT")
|
||||||
: "N/A";
|
: "";
|
||||||
const end_txt = servizio.decorrenza
|
const end_txt = servizio.decorrenza
|
||||||
? new Date(add(servizio.decorrenza, { days: 60 })).toLocaleDateString(
|
? new Date(add(servizio.decorrenza, { days: 60 })).toLocaleDateString(
|
||||||
"it-IT",
|
"it-IT",
|
||||||
)
|
)
|
||||||
: "N/A";
|
: "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full space-y-4">
|
<div className="w-full space-y-4">
|
||||||
<div className="flex flex-col flex-wrap items-start justify-between gap-4 sm:flex-row">
|
<div className="flex flex-col flex-wrap items-start justify-between gap-4 sm:flex-row">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="font-semibold text-xl">
|
<h3 className="font-semibold text-2xl">
|
||||||
{t.servizio.servizio_titolo} {servizio.tipologia}
|
{t.servizio.servizio_titolo} {servizio.tipologia}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
@ -217,51 +227,32 @@ const Content = () => {
|
||||||
<span>{t.servizio.durata_servizio}</span>
|
<span>{t.servizio.durata_servizio}</span>
|
||||||
</div>
|
</div>
|
||||||
<span>
|
<span>
|
||||||
{dec_txt} - {end_txt}
|
{servizio.decorrenza !== null
|
||||||
|
? `${dec_txt} - ${end_txt}`
|
||||||
|
: "In attesa di attivazione"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<ServizioInfos />
|
||||||
<AdminActions />
|
|
||||||
<ServizioBasicActions className="w-full sm:justify-end" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ServizioPacksInfos />
|
|
||||||
<Main />
|
<Main />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Main = () => {
|
const Main = () => {
|
||||||
const { servizio, isAdmin, userId, servizioId } = useServizio();
|
const { servizio, isAdmin } = useServizio();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
|
const { mutate: attivazioneSenzaPagamento } =
|
||||||
onError: (error) => {
|
api.servizio.attivazioneSenzaPagamento.useMutation({
|
||||||
toast.error(error.message);
|
onError: () => {
|
||||||
},
|
toast.error("Errore durante l'attivazione del servizio");
|
||||||
onSuccess: async () => {
|
|
||||||
toast.success("Servizio modificato con successo");
|
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({ servizioId });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutate: removeAnnuncioServizio } =
|
|
||||||
api.servizio.removeAnnuncioServizio.useMutation({
|
|
||||||
onError: (error) => {
|
|
||||||
console.error(error);
|
|
||||||
toast.error("Errore durante la rimozione dell'annuncio");
|
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
toast.success("Servizio modificato con successo");
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
toast.success("Annuncio rimosso");
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -273,93 +264,120 @@ const Main = () => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!servizio.onboardOk) {
|
||||||
if (!servizio.isOkAcconto || servizio.decorrenza === null) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col items-center justify-center gap-4">
|
<div className="flex w-full flex-col items-center justify-center gap-8">
|
||||||
<div className="flex w-full max-w-md flex-col items-center justify-center gap-4 text-center">
|
<div className="flex w-full max-w-md flex-col items-center justify-center gap-4 text-center">
|
||||||
<AlarmClockSVG className="size-46" />
|
<AlarmClockSVG className="size-46" />
|
||||||
<h1 className="text-xl">{t.servizio.non_attivo}</h1>
|
<h1 className="text-xl">{t.servizio.non_attivo}</h1>
|
||||||
<Link
|
<Link
|
||||||
aria-label="Attiva Servizio"
|
aria-label="Attiva Servizio"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
href={`/servizio/onboard/${servizio.servizio_id}`}
|
href={
|
||||||
|
isAdmin
|
||||||
|
? `/area-riservata/admin/user-view/onboard/${servizio.servizio_id}`
|
||||||
|
: `/servizio/onboard/${servizio.servizio_id}`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Button className="w-full text-lg" variant="success">
|
<Button className="w-full text-lg" variant="success">
|
||||||
<span>{t.servizio.attiva_cta}</span>
|
<span>{t.servizio.attiva_cta}</span>
|
||||||
<ArrowRight />
|
<ArrowRight />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
{isAdmin && (
|
<AnnunciAnteprima />
|
||||||
<Button
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!servizio.isOkAcconto || servizio.decorrenza === null) {
|
||||||
|
const acconti = servizio.ordini.filter(
|
||||||
|
(o) => o.type === OrderTypeEnum.Acconto,
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeAcconto = acconti.find((a) => a.isActive);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full flex-col items-center justify-center gap-8">
|
||||||
|
<div className="flex w-full max-w-md flex-col items-center justify-center gap-4 text-center">
|
||||||
|
<PackageCheck className="size-46 stroke-1" />
|
||||||
|
<h1 className="text-xl">Parametri di ricerca salvati</h1>
|
||||||
|
{activeAcconto ? (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
// se acconto inserito, attivato ma servizio non ancora attivo
|
||||||
|
className="w-full text-lg"
|
||||||
|
variant="info"
|
||||||
|
>
|
||||||
|
<span>Attiva il servizio</span>
|
||||||
|
<ArrowRight />
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent className="sm:max-w-xl">
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<div className="mb-4 flex size-9 items-center justify-center rounded-full bg-sky-600/10 sm:mx-0 dark:bg-sky-400/10">
|
||||||
|
<BadgeCheck className="size-4.5 text-sky-600 dark:text-sky-400" />
|
||||||
|
</div>
|
||||||
|
<AlertDialogTitle className="text-xl">
|
||||||
|
Attivazione del Servizio
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Procedendo all'attivazione del servizio, confermi che i
|
||||||
|
parametri di ricerca salvati sono corretti e accetti di
|
||||||
|
essere contattato per eventuali verifiche.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
|
||||||
|
{activeAcconto?.testo_condizioni && (
|
||||||
|
<div className="mt-4 w-full space-y-4">
|
||||||
|
<Link
|
||||||
|
aria-label="Leggi le condizioni"
|
||||||
|
className="block w-fit"
|
||||||
|
href={`/servizio/condizioni/${activeAcconto.testo_condizioni}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<Button className="w-full text-sm">
|
||||||
|
<span>{t.acquisto.leggi_condizioni}</span>
|
||||||
|
<ExternalLink />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Riceverai copia delle condizioni anche via email
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>{t.annulla}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
className="bg-sky-600 text-white hover:bg-sky-600 focus-visible:ring-sky-600 dark:bg-sky-400 dark:focus-visible:ring-sky-400 dark:hover:bg-sky-400"
|
||||||
|
onClick={() => {
|
||||||
|
attivazioneSenzaPagamento({
|
||||||
|
ordineId: activeAcconto.ordine_id,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>Attiva il servizio</span>
|
||||||
|
<ArrowRight className="size-4" />
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
aria-label="Attiva Servizio"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() =>
|
href={`/servizio/pagamento/${servizio.servizio_id}?type=${OrderTypeEnum.Acconto}`}
|
||||||
updateServizio({
|
|
||||||
data: {
|
|
||||||
decorrenza: new Date(),
|
|
||||||
isOkAcconto: true,
|
|
||||||
},
|
|
||||||
servizioId: servizio.servizio_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="success"
|
|
||||||
>
|
>
|
||||||
Attiva Manualmente (Azione Admin)
|
<Button className="w-full text-lg" variant="info">
|
||||||
</Button>
|
<span>Procedi al pagamento</span>
|
||||||
|
<ArrowRight />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{servizio.annunci.length > 0 && (
|
<AnnunciAnteprima />
|
||||||
<div className="w-full space-y-2">
|
|
||||||
<p>
|
|
||||||
{t.servizio.per_te_1} {servizio.annunci.length}{" "}
|
|
||||||
{t.servizio.per_te_2}
|
|
||||||
</p>
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
|
||||||
{servizio.annunci.map((data) => (
|
|
||||||
<div className="group relative" key={data.id}>
|
|
||||||
<AnnuncioDisplay
|
|
||||||
className="bg-[#e6e9ec]/50 dark:bg-card dark:outline dark:outline-foreground/10"
|
|
||||||
data={{
|
|
||||||
...data,
|
|
||||||
modificato_il: data.modificato_il
|
|
||||||
? new Date(data.modificato_il)
|
|
||||||
: null,
|
|
||||||
media_updated_at: data.media_updated_at
|
|
||||||
? new Date(data.media_updated_at)
|
|
||||||
: null,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{isAdmin && (
|
|
||||||
<Confirm
|
|
||||||
description="Sei sicuro di voler rimuovere questo annuncio?"
|
|
||||||
onConfirm={() => {
|
|
||||||
removeAnnuncioServizio({
|
|
||||||
annuncioId: data.annunci_id,
|
|
||||||
servizioId: servizio.servizio_id,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
title="Rimuovi Annuncio"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
aria-label="Rimuovi"
|
|
||||||
className="absolute top-0 right-0 hidden group-hover:flex"
|
|
||||||
size="sm"
|
|
||||||
variant="destructive"
|
|
||||||
>
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</Confirm>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{servizio.annunci.length % 2 === 1 && (
|
|
||||||
<div className="hidden size-full rounded-md border border-(--pattern-fg) bg-[repeating-linear-gradient(45deg,var(--pattern-fg)_0,var(--pattern-fg)_1px,transparent_0,transparent_50%)] bg-size-[10px_10px] bg-fixed opacity-10 [--pattern-fg:#696969] sm:block dark:[--pattern-fg:#e1e1e1]" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -390,7 +408,6 @@ const Main = () => {
|
||||||
data={data}
|
data={data}
|
||||||
key={data.id}
|
key={data.id}
|
||||||
servizioId={servizio.servizio_id}
|
servizioId={servizio.servizio_id}
|
||||||
userId={userId}
|
|
||||||
>
|
>
|
||||||
<AnnuncioCard className="max-w-full" />
|
<AnnuncioCard className="max-w-full" />
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
|
|
@ -434,26 +451,98 @@ const Main = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
|
||||||
{servizio.annunci.map((data) => {
|
{servizio.annunci.map((data) => {
|
||||||
return (
|
return (
|
||||||
<ServizioAnnuncioProvider
|
<ServizioAnnuncioProvider
|
||||||
data={data}
|
data={data}
|
||||||
key={data.id}
|
key={data.id}
|
||||||
servizioId={servizio.servizio_id}
|
servizioId={servizio.servizio_id}
|
||||||
userId={userId}
|
|
||||||
>
|
>
|
||||||
<AnnuncioCard />
|
<AnnuncioCard />
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{servizio.annunci.length % 2 === 1 && (
|
{servizio.annunci.length % 2 === 1 && (
|
||||||
<div className="hidden size-full rounded-md border border-(--pattern-fg) bg-[repeating-linear-gradient(45deg,var(--pattern-fg)_0,var(--pattern-fg)_1px,transparent_0,transparent_50%)] bg-size-[10px_10px] bg-fixed opacity-10 [--pattern-fg:#696969] sm:block dark:[--pattern-fg:#e1e1e1]" />
|
<div className="@sm:block hidden size-full rounded-md border border-(--pattern-fg) bg-[repeating-linear-gradient(45deg,var(--pattern-fg)_0,var(--pattern-fg)_1px,transparent_0,transparent_50%)] bg-size-[10px_10px] bg-fixed opacity-10 [--pattern-fg:#696969] dark:[--pattern-fg:#e1e1e1]" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex flex-col items-center justify-center gap-4 py-4">
|
||||||
|
{servizio.annunci.length === 0 && (
|
||||||
|
<h2 className="font-medium text-lg">Cerca un annuncio ora!</h2>
|
||||||
|
)}
|
||||||
<AnnunciCompatibili />
|
<AnnunciCompatibili />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AnnunciAnteprima = () => {
|
||||||
|
const { servizio, isAdmin } = useServizio();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const { mutate: removeAnnuncioServizio } =
|
||||||
|
api.servizio.removeAnnuncioServizio.useMutation({
|
||||||
|
onError: () => {
|
||||||
|
toast.error("Errore durante la rimozione dell'annuncio dal servizio");
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
toast.success("Annuncio rimosso");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (servizio.annunci.length > 0) {
|
||||||
|
return (
|
||||||
|
<div className="w-full space-y-2">
|
||||||
|
<p>
|
||||||
|
{t.servizio.per_te_1} {servizio.annunci.length} {t.servizio.per_te_2}
|
||||||
|
</p>
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
{servizio.annunci.map((data) => (
|
||||||
|
<div className="group relative" key={data.id}>
|
||||||
|
<AnnuncioDisplay
|
||||||
|
className="bg-[#e6e9ec]/50 dark:bg-card dark:outline dark:outline-foreground/10"
|
||||||
|
data={{
|
||||||
|
...data,
|
||||||
|
modificato_il: data.modificato_il
|
||||||
|
? new Date(data.modificato_il)
|
||||||
|
: null,
|
||||||
|
media_updated_at: data.media_updated_at
|
||||||
|
? new Date(data.media_updated_at)
|
||||||
|
: null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{isAdmin && (
|
||||||
|
<Confirm
|
||||||
|
description="Sei sicuro di voler rimuovere questo annuncio?"
|
||||||
|
onConfirm={() => {
|
||||||
|
removeAnnuncioServizio({
|
||||||
|
annuncioId: data.annunci_id,
|
||||||
|
servizioId: servizio.servizio_id,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
title="Rimuovi Annuncio"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
aria-label="Rimuovi"
|
||||||
|
className="absolute top-0 right-0 hidden group-hover:flex"
|
||||||
|
size="sm"
|
||||||
|
variant="destructive"
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
</Confirm>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{servizio.annunci.length % 2 === 1 && (
|
||||||
|
<div className="hidden size-full rounded-md border border-(--pattern-fg) bg-[repeating-linear-gradient(45deg,var(--pattern-fg)_0,var(--pattern-fg)_1px,transparent_0,transparent_50%)] bg-size-[10px_10px] bg-fixed opacity-10 [--pattern-fg:#696969] sm:block dark:[--pattern-fg:#e1e1e1]" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ import {
|
||||||
Cog,
|
Cog,
|
||||||
Euro,
|
Euro,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
|
Logs,
|
||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
SlidersHorizontal,
|
SlidersHorizontal,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
|
|
@ -55,6 +57,7 @@ import {
|
||||||
type FormValues as EditFormValues,
|
type FormValues as EditFormValues,
|
||||||
FormEditServizio,
|
FormEditServizio,
|
||||||
} from "~/forms/FormEditServizioAdmin";
|
} from "~/forms/FormEditServizioAdmin";
|
||||||
|
import { FormNewOrder } from "~/forms/FormNewOrdine";
|
||||||
import { FormNewServizio, type FormValues } from "~/forms/FormNewServizio";
|
import { FormNewServizio, type FormValues } from "~/forms/FormNewServizio";
|
||||||
import { cn, formatCurrency } from "~/lib/utils";
|
import { cn, formatCurrency } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
|
@ -65,27 +68,76 @@ import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { Confirm } from "../confirm";
|
import { Confirm } from "../confirm";
|
||||||
|
import { Separator } from "../ui/separator";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "../ui/table";
|
||||||
|
|
||||||
export const AdminActions = () => {
|
export const ServizioInfos = () => {
|
||||||
|
return (
|
||||||
|
<div className="grid w-full grid-cols-2 gap-2 sm:grid-cols-4">
|
||||||
|
<ServizioPrezzo />
|
||||||
|
<ServizioActions />
|
||||||
|
<ServizioOrders />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServizioActions = () => {
|
||||||
|
const { isAdmin } = useServizio();
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button className="flex-1" variant="outline">
|
||||||
|
<SlidersHorizontal />
|
||||||
|
<span>Impostazioni</span>
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Azioni Servizio</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">
|
||||||
|
Azioni disponibili per questo servizio
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
{isAdmin && (
|
||||||
|
<div className="w-full text-center">
|
||||||
|
<span>Azioni Admin</span>
|
||||||
|
<Separator />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<AdminActions />
|
||||||
|
{isAdmin && (
|
||||||
|
<div className="w-full text-center">
|
||||||
|
<span>Azioni Utente</span>
|
||||||
|
<Separator />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<UserActions />
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AdminActions = () => {
|
||||||
const { servizio, isAdmin, userId } = useServizio();
|
const { servizio, isAdmin, userId } = useServizio();
|
||||||
if (!isAdmin) return null;
|
if (!isAdmin) return null;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<AddAnnuncio servizioId={servizio.servizio_id} userId={userId} />
|
<AddAnnuncio servizioId={servizio.servizio_id} userId={userId} />
|
||||||
<EditServizioAdmin />
|
<EditServizioAdmin />
|
||||||
<Link
|
|
||||||
href={`/area-riservata/admin/user-view/ordini/${userId}?servizioId=${servizio.servizio_id}`}
|
|
||||||
>
|
|
||||||
<Button variant="warning">
|
|
||||||
<Search />
|
|
||||||
<span>Ordini</span>
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServizioBasicActions = ({ className }: { className?: string }) => {
|
const UserActions = ({ className }: { className?: string }) => {
|
||||||
const { servizio, isAdmin } = useServizio();
|
const { servizio, isAdmin } = useServizio();
|
||||||
|
|
||||||
const canUserEdit = servizio.isOkAcconto && servizio.decorrenza !== null;
|
const canUserEdit = servizio.isOkAcconto && servizio.decorrenza !== null;
|
||||||
|
|
@ -106,6 +158,229 @@ export const ServizioBasicActions = ({ className }: { className?: string }) => {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ServizioPrezzo = () => {
|
||||||
|
const { servizio } = useServizio();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: packs } = api.servizio.getPacksSolution.useQuery({
|
||||||
|
servizioId: servizio.servizio_id,
|
||||||
|
});
|
||||||
|
if (!packs) return null;
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button className="flex-1" variant="outline">
|
||||||
|
<Euro />
|
||||||
|
<span>Prezzi</span>
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Prezzi Servizio</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">
|
||||||
|
Prezzi disponibili per questo servizio
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
{t.servizio.prezzi.sub}
|
||||||
|
</span>
|
||||||
|
<div className="flex w-full flex-col gap-4 md:flex-row">
|
||||||
|
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
|
||||||
|
{packs.acconto.success ? (
|
||||||
|
<>
|
||||||
|
<span className="font-medium">
|
||||||
|
{packs.acconto.pack.nome_it}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
{packs.acconto.pack.desc_it}
|
||||||
|
</span>
|
||||||
|
<span className="font-semibold text-lg">
|
||||||
|
{formatCurrency(packs.acconto.pack.prezzo_cent / 100)}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
COD: {packs.acconto.pack.idprezziario}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className="font-medium">{packs.acconto.message}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
|
||||||
|
{packs.saldo.success ? (
|
||||||
|
<>
|
||||||
|
<span className="font-medium">
|
||||||
|
{packs.saldo.pack.nome_it}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
{packs.saldo.pack.desc_it}
|
||||||
|
</span>
|
||||||
|
<span className="font-semibold text-lg">
|
||||||
|
{formatCurrency(packs.saldo.pack.prezzo_cent / 100)}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
COD: {packs.saldo.pack.idprezziario}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className="font-medium">{packs.saldo.message}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{servizio.tipologia === TipologiaPosizioneEnum.Stabile && (
|
||||||
|
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
|
||||||
|
<>
|
||||||
|
<span className="font-medium">
|
||||||
|
{t.servizio.prezzi.consulenza}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
{t.servizio.prezzi.consulenza_desc}
|
||||||
|
</span>
|
||||||
|
<span className="font-semibold">
|
||||||
|
<Link
|
||||||
|
className="flex items-center gap-2 underline underline-offset-2"
|
||||||
|
href={"/prezzi#contratti"}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<span>{t.servizio.prezzi.gotoprezzi}</span>
|
||||||
|
<ExternalLink className="size-4 stroke-foreground" />
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const ServizioOrders = () => {
|
||||||
|
const { isAdmin, servizio } = useServizio();
|
||||||
|
const [openOrders, setOpenOrders] = useState(false);
|
||||||
|
const [openEdit, setOpenEdit] = useState(false);
|
||||||
|
|
||||||
|
const swapToEdit = () => {
|
||||||
|
setOpenOrders(false);
|
||||||
|
setOpenEdit(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const swapToOrders = () => {
|
||||||
|
setOpenEdit(false);
|
||||||
|
setOpenOrders(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog onOpenChange={setOpenOrders} open={openOrders}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button className="flex-1" variant="outline">
|
||||||
|
<Logs />
|
||||||
|
<span>Ordini</span>
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-2xl">
|
||||||
|
<DialogHeader className="flex-row items-center gap-4">
|
||||||
|
<DialogTitle>Ordini Servizio</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">
|
||||||
|
Ordini disponibili per questo servizio
|
||||||
|
</DialogDescription>
|
||||||
|
{isAdmin && (
|
||||||
|
<Button
|
||||||
|
className="w-fit"
|
||||||
|
onClick={swapToEdit}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
<Plus />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col items-center gap-4 overflow-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Data</TableHead>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
|
<TableHead>Importo</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead></TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{servizio.ordini.length === 0 && (
|
||||||
|
<tr>
|
||||||
|
<td className="py-4 text-center" colSpan={4}>
|
||||||
|
<span>Nessun ordine disponibile</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
{servizio.ordini.map((ordine) => (
|
||||||
|
<TableRow key={ordine.ordine_id}>
|
||||||
|
<TableCell>
|
||||||
|
{ordine.created_at.toLocaleDateString("it-IT")}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="font-medium">
|
||||||
|
{ordine.packid}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{formatCurrency(ordine.amount_cent / 100)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{ordine.isActive ? "Attivo" : "Non attivo"}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Link
|
||||||
|
href={`/area-riservata/${isAdmin ? "admin/user-view/" : ""}ordini${isAdmin ? `/${ordine.userid}` : ""}?ordineId=${ordine.ordine_id}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<Button size="sm" variant="outline">
|
||||||
|
<Search />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog onOpenChange={setOpenEdit} open={openEdit}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Nuovo Ordine</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">nuovo</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<AddOrderDialogContent swapBack={swapToOrders} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddOrderDialogContent = ({ swapBack }: { swapBack: () => void }) => {
|
||||||
|
const { servizioId, userId } = useServizio();
|
||||||
|
const { data, isLoading } = api.prezziario.getPrezziario.useQuery();
|
||||||
|
|
||||||
|
if (isLoading) return <LoadingPage />;
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span>Errore nel caricamento del prezziario.</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormNewOrder
|
||||||
|
prezziario={data}
|
||||||
|
servizioId={servizioId}
|
||||||
|
setOpen={swapBack}
|
||||||
|
userId={userId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ServizioPacksInfos = () => {
|
export const ServizioPacksInfos = () => {
|
||||||
const { servizio } = useServizio();
|
const { servizio } = useServizio();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -203,7 +478,6 @@ export const ServizioPacksInfos = () => {
|
||||||
|
|
||||||
const AddAnnuncio = ({
|
const AddAnnuncio = ({
|
||||||
servizioId,
|
servizioId,
|
||||||
userId,
|
|
||||||
}: {
|
}: {
|
||||||
servizioId: ServizioServizioId;
|
servizioId: ServizioServizioId;
|
||||||
userId: UsersId;
|
userId: UsersId;
|
||||||
|
|
@ -226,15 +500,7 @@ const AddAnnuncio = ({
|
||||||
toast.error("Errore durante l'aggiunta dell'annuncio");
|
toast.error("Errore durante l'aggiunta dell'annuncio");
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
await utils.servizio.getAnnunciAvailableToAdd.invalidate({
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getUserServizi.invalidate({ userId });
|
|
||||||
toast.success("Annuncio aggiunto");
|
toast.success("Annuncio aggiunto");
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
setSelectedAnnunci([]);
|
setSelectedAnnunci([]);
|
||||||
|
|
@ -319,7 +585,7 @@ const AddAnnuncio = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const InterruzioneServizio = () => {
|
const InterruzioneServizio = () => {
|
||||||
const { servizio, userId, isAdmin } = useServizio();
|
const { servizio, isAdmin } = useServizio();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const beforeInterruzioneLimit =
|
const beforeInterruzioneLimit =
|
||||||
servizio.decorrenza != null &&
|
servizio.decorrenza != null &&
|
||||||
|
|
@ -335,10 +601,7 @@ const InterruzioneServizio = () => {
|
||||||
toast.error("Errore durante la richiesta di interruzione");
|
toast.error("Errore durante la richiesta di interruzione");
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getServizioData.invalidate({
|
|
||||||
servizioId: servizio.servizio_id,
|
|
||||||
});
|
|
||||||
toast.success("Interruzione richiesta");
|
toast.success("Interruzione richiesta");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -381,7 +644,7 @@ const InterruzioneServizio = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditParametri = () => {
|
const EditParametri = () => {
|
||||||
const { servizioId, userId, isAdmin, servizio } = useServizio();
|
const { servizioId, isAdmin, servizio } = useServizio();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [openEditParametri, setOpenEditParametri] = useState(false);
|
const [openEditParametri, setOpenEditParametri] = useState(false);
|
||||||
|
|
||||||
|
|
@ -393,13 +656,8 @@ const EditParametri = () => {
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio modificato con successo");
|
toast.success("Servizio modificato con successo");
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
await utils.servizio.invalidate();
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
|
|
||||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({ servizioId });
|
|
||||||
setOpenEditParametri(false);
|
setOpenEditParametri(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -452,22 +710,15 @@ const EditParametri = () => {
|
||||||
const EditServizioAdmin = () => {
|
const EditServizioAdmin = () => {
|
||||||
const { servizioId, userId, servizio } = useServizio();
|
const { servizioId, userId, servizio } = useServizio();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
const router = useRouter();
|
||||||
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio modificato con successo");
|
toast.success("Servizio modificato con successo");
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
await utils.servizio.invalidate();
|
||||||
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
await utils.servizio.getCompatibileAnnunci.invalidate({
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getPacksSolution.invalidate({ servizioId });
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -475,10 +726,14 @@ const EditServizioAdmin = () => {
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
},
|
},
|
||||||
|
onMutate: async () => {
|
||||||
|
await router.push(`/area-riservata/admin/user-view/ricerca/${userId}`);
|
||||||
|
},
|
||||||
|
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio rimosso con successo");
|
toast.success("Servizio rimosso con successo");
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
await utils.servizio.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export const Status500 = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex h-screen max-w-screen-xl items-center justify-start px-4 md:px-8">
|
<div className="mx-auto flex h-screen max-w-7xl items-center justify-start px-4 md:px-8">
|
||||||
<div className="mx-auto max-w-lg space-y-3 text-center">
|
<div className="mx-auto max-w-lg space-y-3 text-center">
|
||||||
<ShieldExclamationIcon className="mx-auto size-20 text-red-600" />
|
<ShieldExclamationIcon className="mx-auto size-20 text-red-600" />
|
||||||
<h3 className="font-semibold text-4xl text-accent-foreground sm:text-5xl">
|
<h3 className="font-semibold text-4xl text-accent-foreground sm:text-5xl">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import type { ColumnDef } from "@tanstack/react-table";
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
import { CircleCheck, ExternalLink } from "lucide-react";
|
import { Circle, CircleCheck, ExternalLink } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { OrdiniModal } from "~/components/area-riservata/ordini";
|
import {
|
||||||
|
AdminWrapperOrdiniModal,
|
||||||
|
UserOrdiniModal,
|
||||||
|
} from "~/components/area-riservata/ordini";
|
||||||
import { DataTable } from "~/components/custom_ui/data-table";
|
import { DataTable } from "~/components/custom_ui/data-table";
|
||||||
import { DataTableColumnHeader } from "~/components/custom_ui/dataTable-header";
|
import { DataTableColumnHeader } from "~/components/custom_ui/dataTable-header";
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -10,28 +13,47 @@ import type {
|
||||||
SearchFiltro,
|
SearchFiltro,
|
||||||
} from "~/components/custom_ui/dataTable-toolbar";
|
} from "~/components/custom_ui/dataTable-toolbar";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { formatCurrency } from "~/lib/utils";
|
||||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { OrdiniWPagamenti } from "~/server/controllers/ordini.controller";
|
import type { Ordini_w_Utente } from "~/server/controllers/ordini.controller";
|
||||||
|
|
||||||
type PaymentsTableProps = {
|
type PaymentsTableProps = {
|
||||||
data: OrdiniWPagamenti[];
|
data: Ordini_w_Utente[];
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
userId?: UsersId;
|
userId?: UsersId;
|
||||||
|
filter: OrdiniOrdineId | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
const StatusMapping = (status: PaymentStatusEnum | null) => {
|
||||||
|
switch (status) {
|
||||||
|
case PaymentStatusEnum.success:
|
||||||
|
return "Successo";
|
||||||
|
case PaymentStatusEnum.processing:
|
||||||
|
return "In lavorazione";
|
||||||
|
case PaymentStatusEnum.failed:
|
||||||
|
return "Errore";
|
||||||
|
default:
|
||||||
|
return "Non completato";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OrdiniTable = ({
|
||||||
|
data,
|
||||||
|
isAdmin,
|
||||||
|
userId,
|
||||||
|
filter,
|
||||||
|
}: PaymentsTableProps) => {
|
||||||
const tabledata = data.map((row) => {
|
const tabledata = data.map((row) => {
|
||||||
return {
|
return {
|
||||||
actions: "Azioni",
|
actions: "Azioni",
|
||||||
created_at: row.created_at,
|
created_at: row.created_at,
|
||||||
id: row.ordine_id,
|
id: row.ordine_id,
|
||||||
isActive: row.isActive,
|
status: row.isActive,
|
||||||
isPaid: row.pagamenti.some(
|
payment_status: row.paymentstatus,
|
||||||
(p) => p.paymentstatus === PaymentStatusEnum.success,
|
importo: row.amount_cent,
|
||||||
),
|
|
||||||
packid: row.packid,
|
packid: row.packid,
|
||||||
tipo: row.type,
|
tipo: row.type,
|
||||||
userId: row.userid,
|
userId: row.userid,
|
||||||
|
|
@ -40,16 +62,19 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns_titles = {
|
const columns_titles = {
|
||||||
|
id: "ID Ordine",
|
||||||
actions: "Azioni",
|
actions: "Azioni",
|
||||||
created_at: "Creato il",
|
created_at: "Creato il",
|
||||||
isPaid: "Pagato",
|
importo: "Importo",
|
||||||
|
status: "Status",
|
||||||
|
payment_status: "Pagamento",
|
||||||
packid: "ID Pacchetto",
|
packid: "ID Pacchetto",
|
||||||
tipo: "Tipo",
|
tipo: "Tipo",
|
||||||
username: "Utente",
|
username: "Utente",
|
||||||
};
|
};
|
||||||
const isPaidOptions = [
|
const statusOptions = [
|
||||||
{ label: "Pagato", value: "true" },
|
{ label: "Attivo", value: "true" },
|
||||||
{ label: "Non pagato", value: "false" },
|
{ label: "Non attivo", value: "false" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const type_options = [
|
const type_options = [
|
||||||
|
|
@ -59,7 +84,6 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
||||||
{ label: "Altro", value: OrderTypeEnum.Altro },
|
{ label: "Altro", value: OrderTypeEnum.Altro },
|
||||||
];
|
];
|
||||||
|
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
|
||||||
const [selectedOrdine, setSelectedOrdine] = useState<OrdiniOrdineId | null>(
|
const [selectedOrdine, setSelectedOrdine] = useState<OrdiniOrdineId | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
@ -67,6 +91,13 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
||||||
type Column = (typeof tabledata)[number];
|
type Column = (typeof tabledata)[number];
|
||||||
|
|
||||||
const columns: ColumnDef<Column>[] = [
|
const columns: ColumnDef<Column>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
cell: () => null,
|
||||||
|
enableHiding: false,
|
||||||
|
header: () => null,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "username",
|
accessorKey: "username",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
|
@ -125,30 +156,35 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "isPaid",
|
accessorKey: "importo",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const isPaid = row.original.isPaid;
|
return <span>{formatCurrency(row.original.importo / 100)}</span>;
|
||||||
const isActive = row.original.isActive;
|
},
|
||||||
if (isActive) {
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader column={column} title={columns_titles.importo} />
|
||||||
|
),
|
||||||
|
enableHiding: false,
|
||||||
|
sortingFn: (a, b, id) => {
|
||||||
|
const aValue = a.getValue(id) as number;
|
||||||
|
const bValue = b.getValue(id) as number;
|
||||||
|
return aValue - bValue;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "status",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
if (!row.original.status) {
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 text-green-500">
|
<span className="inline-flex items-center gap-1">
|
||||||
<CircleCheck className="size-5" />
|
<Circle className="size-5" />
|
||||||
{isPaid ? "Pagato" : "Attivato da Admin"}
|
Non attivo
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (isPaid) {
|
|
||||||
return (
|
|
||||||
<span className="inline-flex items-center gap-1 text-green-500">
|
|
||||||
<CircleCheck className="size-5" />
|
|
||||||
Pagato
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 text-red-500">
|
<span className="inline-flex items-center gap-1 text-green-500">
|
||||||
<CircleCheck className="size-5" />
|
<CircleCheck className="size-5" />
|
||||||
Non pagato
|
Attivo
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -157,10 +193,25 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
||||||
return value.includes(row.getValue(id));
|
return value.includes(row.getValue(id));
|
||||||
},
|
},
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader column={column} title={columns_titles.isPaid} />
|
<DataTableColumnHeader column={column} title={columns_titles.status} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "payment_status",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <span>{StatusMapping(row.original.payment_status)}</span>;
|
||||||
|
},
|
||||||
|
|
||||||
|
filterFn: (row, id, value) => {
|
||||||
|
return value.includes(row.getValue(id));
|
||||||
|
},
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
column={column}
|
||||||
|
title={columns_titles.payment_status}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -168,13 +219,11 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
||||||
aria-label="Apri Dettagli Ordine"
|
aria-label="Apri Dettagli Ordine"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedOrdine(row.original.id);
|
setSelectedOrdine(row.original.id);
|
||||||
setModalOpen(true);
|
|
||||||
}}
|
}}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<ExternalLink className="size-6" />
|
<ExternalLink className="size-5" />
|
||||||
Apri Dettagli
|
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -183,23 +232,17 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const searchFiltro: SearchFiltro =
|
const searchFiltro: SearchFiltro = {
|
||||||
isAdmin && !userId
|
columnName: "id",
|
||||||
? {
|
placeholder: "Cerca ordine...",
|
||||||
columnName: "username",
|
};
|
||||||
placeholder: "Cerca utente...",
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
columnName: "packid",
|
|
||||||
placeholder: "Cerca pacchetto...",
|
|
||||||
};
|
|
||||||
|
|
||||||
const pinnedFiltri: PinnedFiltro[] = [
|
const pinnedFiltri: PinnedFiltro[] = [
|
||||||
{
|
{
|
||||||
columnName: "isPaid",
|
columnName: "status",
|
||||||
|
|
||||||
options: isPaidOptions,
|
options: statusOptions,
|
||||||
title: "Stato Pagamento",
|
title: "Stato Ordine",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
columnName: "tipo",
|
columnName: "tipo",
|
||||||
|
|
@ -223,18 +266,25 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
|
||||||
tipo: true,
|
tipo: true,
|
||||||
username: isAdmin && !userId,
|
username: isAdmin && !userId,
|
||||||
}}
|
}}
|
||||||
|
defaultFilterState={
|
||||||
|
filter ? [{ id: "id", value: filter.toString() }] : []
|
||||||
|
}
|
||||||
defaultSort={[{ desc: true, id: "created_at" }]}
|
defaultSort={[{ desc: true, id: "created_at" }]}
|
||||||
pinnedFiltri={pinnedFiltri}
|
pinnedFiltri={pinnedFiltri}
|
||||||
searchColumn={searchFiltro}
|
searchColumn={searchFiltro}
|
||||||
/>
|
/>
|
||||||
{selectedOrdine && (
|
{isAdmin ? (
|
||||||
<OrdiniModal
|
<AdminWrapperOrdiniModal
|
||||||
isAdmin={isAdmin}
|
|
||||||
open={modalOpen}
|
|
||||||
ordineId={selectedOrdine}
|
ordineId={selectedOrdine}
|
||||||
setModalOpen={setModalOpen}
|
setSelected={setSelectedOrdine}
|
||||||
userId={userId}
|
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
selectedOrdine && (
|
||||||
|
<UserOrdiniModal
|
||||||
|
ordineId={selectedOrdine}
|
||||||
|
setSelected={setSelectedOrdine}
|
||||||
|
/>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
import type { ColumnDef } from "@tanstack/react-table";
|
|
||||||
import { EllipsisVertical, Trash2, Wrench } from "lucide-react";
|
|
||||||
import { DataTable } from "~/components/custom_ui/data-table";
|
|
||||||
import { DataTableColumnHeader } from "~/components/custom_ui/dataTable-header";
|
|
||||||
import type {
|
|
||||||
PinnedFiltro,
|
|
||||||
SearchFiltro,
|
|
||||||
} from "~/components/custom_ui/dataTable-toolbar";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuPortal,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuSub,
|
|
||||||
DropdownMenuSubContent,
|
|
||||||
DropdownMenuSubTrigger,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "~/components/ui/dropdown-menu";
|
|
||||||
import { formatCurrency } from "~/lib/utils";
|
|
||||||
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
|
||||||
import type { OrdiniWPagamenti } from "~/server/controllers/ordini.controller";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
|
|
||||||
type PaymentsTableProps = {
|
|
||||||
data: OrdiniWPagamenti["pagamenti"];
|
|
||||||
isAdmin: boolean;
|
|
||||||
isUserView?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StatusMapping = (status: PaymentStatusEnum | null) => {
|
|
||||||
switch (status) {
|
|
||||||
case PaymentStatusEnum.success:
|
|
||||||
return "Successo";
|
|
||||||
case PaymentStatusEnum.processing:
|
|
||||||
return "In lavorazione";
|
|
||||||
case PaymentStatusEnum.failed:
|
|
||||||
return "Errore";
|
|
||||||
default:
|
|
||||||
return "Non completato";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const EnumFromString = (value: string) => {
|
|
||||||
switch (value) {
|
|
||||||
case "Successo":
|
|
||||||
return PaymentStatusEnum.success;
|
|
||||||
case "In lavorazione":
|
|
||||||
return PaymentStatusEnum.processing;
|
|
||||||
case "Errore":
|
|
||||||
return PaymentStatusEnum.failed;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PaymentsTable = ({
|
|
||||||
data,
|
|
||||||
isAdmin,
|
|
||||||
isUserView = false,
|
|
||||||
}: PaymentsTableProps) => {
|
|
||||||
const tabledata = data.map((row) => {
|
|
||||||
return {
|
|
||||||
actions: "Azioni",
|
|
||||||
amount: row.amount_cent,
|
|
||||||
id: row.id,
|
|
||||||
paid_at: row.paid_at,
|
|
||||||
raw_status: row.paymentstatus,
|
|
||||||
status: StatusMapping(row.paymentstatus),
|
|
||||||
intent_id: row.intent_id,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns_titles = {
|
|
||||||
actions: "Azioni",
|
|
||||||
amount: "Importo",
|
|
||||||
paid_at: "Pagato il",
|
|
||||||
status: "Stato",
|
|
||||||
username: "Utente",
|
|
||||||
};
|
|
||||||
const status_options = [
|
|
||||||
{ label: "Inutilizzato", value: "Inutilizzato" },
|
|
||||||
{ label: "Successo", value: "Successo" },
|
|
||||||
{ label: "In lavorazione", value: "In lavorazione" },
|
|
||||||
{ label: "Errore", value: "Errore" },
|
|
||||||
];
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const { mutate: update } = api.servizio.updatePagamento.useMutation({
|
|
||||||
onSuccess: async () => {
|
|
||||||
await utils.servizio.getAllPagamenti.invalidate();
|
|
||||||
await utils.servizio.getOrdini.invalidate();
|
|
||||||
await utils.servizio.getOrdineById.invalidate({
|
|
||||||
ordineId: data[0]?.ordine_id,
|
|
||||||
});
|
|
||||||
if ((!isAdmin || isUserView) && data[0]) {
|
|
||||||
await utils.servizio.getUserPagamenti.invalidate({
|
|
||||||
userId: data[0].userid,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { mutate: remove } = api.servizio.removePagamento.useMutation({
|
|
||||||
onSuccess: async () => {
|
|
||||||
await utils.servizio.getAllPagamenti.invalidate();
|
|
||||||
await utils.servizio.getOrdini.invalidate();
|
|
||||||
await utils.servizio.getOrdineById.invalidate({
|
|
||||||
ordineId: data[0]?.ordine_id,
|
|
||||||
});
|
|
||||||
if ((!isAdmin || isUserView) && data[0]) {
|
|
||||||
await utils.servizio.getUserPagamenti.invalidate({
|
|
||||||
userId: data[0].userid,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
type Column = (typeof tabledata)[number];
|
|
||||||
|
|
||||||
const columns: ColumnDef<Column>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "paid_at",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const date = row.getValue("paid_at");
|
|
||||||
return new Date(date as Date).toLocaleDateString();
|
|
||||||
},
|
|
||||||
enableHiding: false,
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader column={column} title={columns_titles.paid_at} />
|
|
||||||
),
|
|
||||||
sortingFn: "datetime",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "status",
|
|
||||||
|
|
||||||
filterFn: (row, id, value) => {
|
|
||||||
return value.includes(row.getValue(id));
|
|
||||||
},
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader column={column} title={columns_titles.status} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "amount",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return <span>{formatCurrency(row.original.amount / 100)}</span>;
|
|
||||||
},
|
|
||||||
header: ({ column }) => (
|
|
||||||
<DataTableColumnHeader column={column} title={columns_titles.amount} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const data = row.original;
|
|
||||||
if (!isAdmin) return null;
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button className="size-8 p-0" variant="ghost">
|
|
||||||
<span className="sr-only">Open menu</span>
|
|
||||||
<EllipsisVertical />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuLabel>Azioni</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuSub>
|
|
||||||
<DropdownMenuSubTrigger>
|
|
||||||
<Wrench />
|
|
||||||
<span>Aggiorna status</span>
|
|
||||||
</DropdownMenuSubTrigger>
|
|
||||||
<DropdownMenuPortal>
|
|
||||||
<DropdownMenuSubContent>
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
onValueChange={(v) => {
|
|
||||||
update({
|
|
||||||
data: {
|
|
||||||
paymentstatus: EnumFromString(v),
|
|
||||||
paid_at: v === "Successo" ? new Date() : null,
|
|
||||||
},
|
|
||||||
pagamentoId: data.id,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
value={StatusMapping(data.raw_status)}
|
|
||||||
>
|
|
||||||
<DropdownMenuRadioItem value="Inutilizzato">
|
|
||||||
Inutilizzato
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
|
|
||||||
<DropdownMenuRadioItem value="Successo">
|
|
||||||
Successo
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="In lavorazione">
|
|
||||||
In lavorazione
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="Errore">
|
|
||||||
Errore
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuSubContent>
|
|
||||||
</DropdownMenuPortal>
|
|
||||||
</DropdownMenuSub>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
aria-label="Elimina Pagamento"
|
|
||||||
className="flex items-center gap-3 hover:cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
confirm("Sei sicuro di voler eliminare il servizio?") &&
|
|
||||||
remove({ pagamentoId: data.id })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Trash2 className="size-5" />
|
|
||||||
Elimina Pagamento
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
enableHiding: false,
|
|
||||||
id: "actions",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const searchFiltro: SearchFiltro | undefined =
|
|
||||||
isAdmin && !isUserView
|
|
||||||
? {
|
|
||||||
columnName: "username",
|
|
||||||
placeholder: "Cerca utente...",
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const pinnedFiltri: PinnedFiltro[] = [
|
|
||||||
{
|
|
||||||
columnName: "status",
|
|
||||||
options: status_options,
|
|
||||||
title: "Status",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full py-6">
|
|
||||||
<DataTable
|
|
||||||
columns={columns}
|
|
||||||
columns_titles={columns_titles}
|
|
||||||
data={tabledata}
|
|
||||||
defaultColumnVisibility={{
|
|
||||||
actions: true,
|
|
||||||
amount: true,
|
|
||||||
packid: true,
|
|
||||||
paid_at: true,
|
|
||||||
status: true,
|
|
||||||
username: isAdmin && !isUserView,
|
|
||||||
}}
|
|
||||||
defaultSort={[{ desc: true, id: "paid_at" }]}
|
|
||||||
pinnedFiltri={pinnedFiltri}
|
|
||||||
searchColumn={searchFiltro}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
import { buttonVariants } from "~/components/ui/button";
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
import { Button } from "./button";
|
||||||
|
|
||||||
function AlertDialog({
|
function AlertDialog({
|
||||||
...props
|
...props
|
||||||
|
|
@ -32,7 +34,7 @@ function AlertDialogOverlay({
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Overlay
|
<AlertDialogPrimitive.Overlay
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in",
|
"data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
data-slot="alert-dialog-overlay"
|
data-slot="alert-dialog-overlay"
|
||||||
|
|
@ -43,16 +45,20 @@ function AlertDialogOverlay({
|
||||||
|
|
||||||
function AlertDialogContent({
|
function AlertDialogContent({
|
||||||
className,
|
className,
|
||||||
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
|
||||||
|
size?: "default" | "sm";
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<AlertDialogOverlay />
|
<AlertDialogOverlay />
|
||||||
<AlertDialogPrimitive.Content
|
<AlertDialogPrimitive.Content
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg",
|
"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-background p-4 outline-none ring-1 ring-foreground/10 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-closed:animate-out data-open:animate-in data-[size=default]:sm:max-w-sm",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
data-size={size}
|
||||||
data-slot="alert-dialog-content"
|
data-slot="alert-dialog-content"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
@ -66,7 +72,10 @@ function AlertDialogHeader({
|
||||||
}: React.ComponentProps<"div">) {
|
}: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
className={cn(
|
||||||
|
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
data-slot="alert-dialog-header"
|
data-slot="alert-dialog-header"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
@ -80,7 +89,7 @@ function AlertDialogFooter({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
data-slot="alert-dialog-footer"
|
data-slot="alert-dialog-footer"
|
||||||
|
|
@ -89,13 +98,32 @@ function AlertDialogFooter({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AlertDialogMedia({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
data-slot="alert-dialog-media"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function AlertDialogTitle({
|
function AlertDialogTitle({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Title
|
<AlertDialogPrimitive.Title
|
||||||
className={cn("font-semibold text-lg", className)}
|
className={cn(
|
||||||
|
"font-medium text-base sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
data-slot="alert-dialog-title"
|
data-slot="alert-dialog-title"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
@ -108,7 +136,10 @@ function AlertDialogDescription({
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Description
|
<AlertDialogPrimitive.Description
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn(
|
||||||
|
"text-balance text-muted-foreground text-sm md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
data-slot="alert-dialog-description"
|
data-slot="alert-dialog-description"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
@ -117,38 +148,51 @@ function AlertDialogDescription({
|
||||||
|
|
||||||
function AlertDialogAction({
|
function AlertDialogAction({
|
||||||
className,
|
className,
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
|
||||||
|
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Action
|
<Button asChild size={size} variant={variant}>
|
||||||
className={cn(buttonVariants(), className)}
|
<AlertDialogPrimitive.Action
|
||||||
{...props}
|
className={cn(className)}
|
||||||
/>
|
data-slot="alert-dialog-action"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDialogCancel({
|
function AlertDialogCancel({
|
||||||
className,
|
className,
|
||||||
|
variant = "outline",
|
||||||
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
|
||||||
|
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
||||||
return (
|
return (
|
||||||
<AlertDialogPrimitive.Cancel
|
<Button asChild size={size} variant={variant}>
|
||||||
className={cn(buttonVariants({ variant: "outline" }), className)}
|
<AlertDialogPrimitive.Cancel
|
||||||
{...props}
|
className={cn(className)}
|
||||||
/>
|
data-slot="alert-dialog-cancel"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogPortal,
|
|
||||||
AlertDialogOverlay,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
AlertDialogCancel,
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogMedia,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogPortal,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={`/area-riservata/admin/scheda-annuncio-stampa/${data.id}`}
|
href={`/area-riservata/scheda-annuncio-stampa/${data.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Button size="sm" type="button" variant="outline">
|
<Button size="sm" type="button" variant="outline">
|
||||||
|
|
|
||||||
354
apps/infoalloggi/src/forms/FormEditOrdine.tsx
Normal file
354
apps/infoalloggi/src/forms/FormEditOrdine.tsx
Normal file
|
|
@ -0,0 +1,354 @@
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { it } from "date-fns/locale";
|
||||||
|
import { CalendarIcon, Trash2 } from "lucide-react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
import { Confirm } from "~/components/confirm";
|
||||||
|
import {
|
||||||
|
CredenzaBody,
|
||||||
|
CredenzaClose,
|
||||||
|
CredenzaFooter,
|
||||||
|
} from "~/components/custom_ui/credenza";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "~/components/custom_ui/form";
|
||||||
|
import { MultiSelect } from "~/components/custom_ui/multiselect";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { Calendar } from "~/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "~/components/ui/popover";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "~/components/ui/select";
|
||||||
|
import { Switch } from "~/components/ui/switch";
|
||||||
|
import { PAYMENT_TYPES, type PaymentType } from "~/i18n/stripe";
|
||||||
|
import { cn, formatCurrency } from "~/lib/utils";
|
||||||
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
|
import type { Ordini } from "~/schemas/public/Ordini";
|
||||||
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
||||||
|
import type { Prezziario } from "~/schemas/public/Prezziario";
|
||||||
|
import { zPrezziarioId } from "~/server/utils/zod_types";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
|
type FormProps = {
|
||||||
|
prezziario: Prezziario[];
|
||||||
|
data: Ordini;
|
||||||
|
close: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormEditOrder = ({ prezziario, data, close }: FormProps) => {
|
||||||
|
const schema = z.object({
|
||||||
|
packid: zPrezziarioId,
|
||||||
|
isActive: z.boolean(),
|
||||||
|
type: z.enum(OrderTypeEnum),
|
||||||
|
created_at: z.date(),
|
||||||
|
amount_cent: z.number(),
|
||||||
|
sconto: z.number().min(0).max(100),
|
||||||
|
paid_at: z.date().nullable(),
|
||||||
|
paymentmethod: z.enum(PAYMENT_TYPES).nullable(),
|
||||||
|
paymentstatus: z.enum(PaymentStatusEnum).nullable(),
|
||||||
|
});
|
||||||
|
type FormValues = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
z.config(z.locales.it());
|
||||||
|
|
||||||
|
const form = useZodForm(schema, {
|
||||||
|
defaultValues: {
|
||||||
|
...data,
|
||||||
|
paymentmethod: (data.paymentmethod as PaymentType) || null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const { mutate: updateOrder } = api.servizio.updateOrder.useMutation({
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(`Errore durante la modifica: ${error.message}`);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
toast.success("Ordine aggiornato con successo!");
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { mutate: remove } = api.servizio.removeOrder.useMutation({
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(`Errore durante la rimozione ordine: ${error.message}`);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
toast.success("Ordine rimosso con successo!");
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { watch } = form;
|
||||||
|
|
||||||
|
const scontoWch = watch(["sconto", "amount_cent"]);
|
||||||
|
function onSubmit(fields: FormValues) {
|
||||||
|
updateOrder({
|
||||||
|
ordineId: data.ordine_id,
|
||||||
|
data: fields,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
|
<CredenzaBody className="max-h-[80vh] w-full pb-5">
|
||||||
|
<div className="space-y-8 px-0.5">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="packid"
|
||||||
|
render={({ field }) => {
|
||||||
|
const pack = prezziario.find(
|
||||||
|
(p) => p.idprezziario === field.value,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<FormItem className="w-full">
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel htmlFor="select-pack">Servizio</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<MultiSelect
|
||||||
|
defaultValue={
|
||||||
|
field.value
|
||||||
|
? {
|
||||||
|
label: pack?.nome_it || "",
|
||||||
|
value: field.value,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
elementId="select-pack"
|
||||||
|
elementName="packid"
|
||||||
|
isMulti={false}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const selected = zPrezziarioId.safeParse(
|
||||||
|
value.value,
|
||||||
|
);
|
||||||
|
if (selected.success) {
|
||||||
|
field.onChange(selected.data);
|
||||||
|
const selectedPrezzo = prezziario.find(
|
||||||
|
(p) => p.idprezziario === selected.data,
|
||||||
|
);
|
||||||
|
if (selectedPrezzo) {
|
||||||
|
form.setValue(
|
||||||
|
"amount_cent",
|
||||||
|
selectedPrezzo.prezzo_cent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Invalid packid selected",
|
||||||
|
value.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={prezziario.map((p) => ({
|
||||||
|
label: `${p.idprezziario} - ${p.nome_it} (${formatCurrency(p.prezzo_cent / 100)})`,
|
||||||
|
value: p.idprezziario,
|
||||||
|
}))}
|
||||||
|
placeholder="Seleziona..."
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="type"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel htmlFor="titolo">Tipologia</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value || ""}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue placeholder="Seleziona tipologia" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent id="select-color">
|
||||||
|
{Object.values(OrderTypeEnum).map((option) => (
|
||||||
|
<SelectItem key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="created_at"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex w-full flex-col">
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel htmlFor="created_at">Data Ordine</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
className={cn(
|
||||||
|
"h-10.5 w-full pl-3 text-left font-medium",
|
||||||
|
!field.value && "text-muted-foreground",
|
||||||
|
"rounded-lg border border-neutral-300 shadow-xs outline-hidden file:border-0 file:bg-transparent file:font-medium file:text-sm disabled:cursor-not-allowed disabled:opacity-50 dark:focus:border-transparent",
|
||||||
|
)}
|
||||||
|
id="created_at"
|
||||||
|
variant={"outline"}
|
||||||
|
>
|
||||||
|
{field.value ? (
|
||||||
|
format(field.value, "PPP", {
|
||||||
|
locale: it,
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<span>Scegli data</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="ml-auto size-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent align="start" className="w-auto p-0">
|
||||||
|
<Calendar
|
||||||
|
autoFocus
|
||||||
|
captionLayout="dropdown"
|
||||||
|
defaultMonth={new Date()}
|
||||||
|
endMonth={new Date(2050, 12)}
|
||||||
|
locale={it}
|
||||||
|
mode="single"
|
||||||
|
onSelect={field.onChange}
|
||||||
|
selected={field.value || undefined}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="isActive"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-full">
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel htmlFor="isActive">Pagato</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
className="data-[state=checked]:bg-neutral-700"
|
||||||
|
defaultChecked={field.value}
|
||||||
|
id="isActive"
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
Imposta se il servizio è pagato
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="sconto"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel htmlFor="sconto">Sconto</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
defaultValue={String(field.value)}
|
||||||
|
name="sconto"
|
||||||
|
onValueChange={(value) => {
|
||||||
|
field.onChange(Number.parseInt(value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-45">
|
||||||
|
<SelectValue placeholder="Sconto" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{[
|
||||||
|
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60,
|
||||||
|
65, 70, 75, 80, 85, 90, 95, 100,
|
||||||
|
].map((v) => {
|
||||||
|
return (
|
||||||
|
<SelectItem key={v} value={String(v)}>
|
||||||
|
{v}%
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{scontoWch && scontoWch[0] !== 0 ? (
|
||||||
|
<span>
|
||||||
|
Prezzo scontato:{" "}
|
||||||
|
{formatCurrency(
|
||||||
|
((1 - scontoWch[0] / 100) * scontoWch[1]) / 100,
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>Prezzo: {formatCurrency(scontoWch[1] / 100)}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CredenzaBody>
|
||||||
|
|
||||||
|
<CredenzaFooter className="flex w-full sm:justify-between">
|
||||||
|
<Confirm
|
||||||
|
description="Sei sicuro di voler eliminare l'ordine?"
|
||||||
|
onConfirm={() => {
|
||||||
|
remove({ ordineId: data.ordine_id });
|
||||||
|
}}
|
||||||
|
title="Elimina ordine"
|
||||||
|
>
|
||||||
|
<Button aria-label="Elimina Ordine" variant="destructive">
|
||||||
|
<Trash2 className="size-6" />
|
||||||
|
Elimina Ordine
|
||||||
|
</Button>
|
||||||
|
</Confirm>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button type="submit" variant="success">
|
||||||
|
Salva
|
||||||
|
</Button>
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button aria-label="Close Credenza" variant="outline">
|
||||||
|
Chiudi
|
||||||
|
</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
</div>
|
||||||
|
</CredenzaFooter>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -35,6 +35,7 @@ const Schema = z.object({
|
||||||
skipPayment: z.boolean(),
|
skipPayment: z.boolean(),
|
||||||
skipControlloDoc: z.boolean(),
|
skipControlloDoc: z.boolean(),
|
||||||
skipDocMotivazione: z.boolean(),
|
skipDocMotivazione: z.boolean(),
|
||||||
|
onboardOk: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormValues = z.infer<typeof Schema>;
|
export type FormValues = z.infer<typeof Schema>;
|
||||||
|
|
@ -64,6 +65,7 @@ export const FormEditServizio = ({
|
||||||
skipPayment: false,
|
skipPayment: false,
|
||||||
skipControlloDoc: false,
|
skipControlloDoc: false,
|
||||||
skipDocMotivazione: false,
|
skipDocMotivazione: false,
|
||||||
|
onboardOk: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { locale, t } = useTranslation();
|
const { locale, t } = useTranslation();
|
||||||
|
|
@ -193,6 +195,28 @@ export const FormEditServizio = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
|
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="onboardOk"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-full">
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel htmlFor="onboardOk">Onboard Fatta</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
className="data-[state=checked]:bg-neutral-700"
|
||||||
|
defaultChecked={field.value}
|
||||||
|
id="onboardOk"
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormDescription>Il servizio è interrotto</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="isOkAcconto"
|
name="isOkAcconto"
|
||||||
|
|
|
||||||
|
|
@ -58,15 +58,21 @@ export const FormNewOrder = ({
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
packid: zPrezziarioId,
|
packid: zPrezziarioId,
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
type: z.custom<OrderTypeEnum>(),
|
type: z.enum(OrderTypeEnum),
|
||||||
created_at: z.date(),
|
created_at: z.date(),
|
||||||
|
amount_cent: z.number(),
|
||||||
|
sconto: z.number().min(0).max(100),
|
||||||
});
|
});
|
||||||
type FormValues = z.infer<typeof schema>;
|
type FormValues = z.infer<typeof schema>;
|
||||||
|
|
||||||
z.config(z.locales.it());
|
z.config(z.locales.it());
|
||||||
|
|
||||||
const form = useZodForm(schema, {
|
const form = useZodForm(schema, {
|
||||||
defaultValues: {},
|
defaultValues: {
|
||||||
|
sconto: 0,
|
||||||
|
amount_cent: 0,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate: createOrder } = api.servizio.createOrdine.useMutation({
|
const { mutate: createOrder } = api.servizio.createOrdine.useMutation({
|
||||||
|
|
@ -75,20 +81,17 @@ export const FormNewOrder = ({
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success("Ordine creato con successo");
|
toast.success("Ordine creato con successo");
|
||||||
await utils.servizio.getOrdini.invalidate({
|
await utils.servizio.invalidate();
|
||||||
userId,
|
|
||||||
});
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
form.reset();
|
form.reset();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const { watch } = form;
|
||||||
|
|
||||||
|
const scontoWch = watch(["sconto", "amount_cent"]);
|
||||||
function onSubmit(fields: FormValues) {
|
function onSubmit(fields: FormValues) {
|
||||||
createOrder({
|
createOrder({
|
||||||
data: {
|
data: { ...fields, servizio_id: servizioId, userid: userId },
|
||||||
...fields,
|
|
||||||
servizio_id: servizioId,
|
|
||||||
userid: userId,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,6 +120,15 @@ export const FormNewOrder = ({
|
||||||
const selected = zPrezziarioId.safeParse(value.value);
|
const selected = zPrezziarioId.safeParse(value.value);
|
||||||
if (selected.success) {
|
if (selected.success) {
|
||||||
field.onChange(selected.data);
|
field.onChange(selected.data);
|
||||||
|
const selectedPrezzo = prezziario.find(
|
||||||
|
(p) => p.idprezziario === selected.data,
|
||||||
|
);
|
||||||
|
if (selectedPrezzo) {
|
||||||
|
form.setValue(
|
||||||
|
"amount_cent",
|
||||||
|
selectedPrezzo.prezzo_cent,
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
"Invalid packid selected",
|
"Invalid packid selected",
|
||||||
|
|
@ -220,7 +232,7 @@ export const FormNewOrder = ({
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="w-full">
|
<FormItem className="w-full">
|
||||||
<div className="flex flex-wrap items-center gap-x-2">
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
<FormLabel htmlFor="isActive">Attivo</FormLabel>
|
<FormLabel htmlFor="isActive">Pagato</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch
|
||||||
className="data-[state=checked]:bg-neutral-700"
|
className="data-[state=checked]:bg-neutral-700"
|
||||||
|
|
@ -233,11 +245,59 @@ export const FormNewOrder = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Imposta se il servizio è attivo
|
Imposta se il servizio è pagato
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="sconto"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel htmlFor="sconto">Sconto</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
defaultValue={String(field.value)}
|
||||||
|
name="sconto"
|
||||||
|
onValueChange={(value) => {
|
||||||
|
field.onChange(Number.parseInt(value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-45">
|
||||||
|
<SelectValue placeholder="Sconto" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{[
|
||||||
|
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60,
|
||||||
|
65, 70, 75, 80, 85, 90, 95, 100,
|
||||||
|
].map((v) => {
|
||||||
|
return (
|
||||||
|
<SelectItem key={v} value={String(v)}>
|
||||||
|
{v}%
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{scontoWch && scontoWch[0] !== 0 ? (
|
||||||
|
<span>
|
||||||
|
Prezzo scontato:{" "}
|
||||||
|
{formatCurrency(
|
||||||
|
((1 - scontoWch[0] / 100) * scontoWch[1]) / 100,
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>Prezzo: {formatCurrency(scontoWch[1] / 100)}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CredenzaBody>
|
</CredenzaBody>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,14 +114,15 @@ type FormNewServizioValues = z.infer<typeof FormNewServizioSchema>;
|
||||||
export const FormNewServizioAcquisto = ({
|
export const FormNewServizioAcquisto = ({
|
||||||
initialData,
|
initialData,
|
||||||
userId,
|
userId,
|
||||||
isAdmin = false,
|
isAdmin,
|
||||||
}: {
|
}: {
|
||||||
initialData: InitialData;
|
initialData: InitialData;
|
||||||
userId: UsersId;
|
userId: UsersId;
|
||||||
isAdmin?: boolean;
|
isAdmin: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { comuni, nazioni } = useCatasto();
|
const { comuni, nazioni } = useCatasto();
|
||||||
|
console.log(isAdmin);
|
||||||
const schema = FormNewServizioSchema.superRefine(
|
const schema = FormNewServizioSchema.superRefine(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
|
|
@ -226,14 +227,7 @@ export const FormNewServizioAcquisto = ({
|
||||||
path: ["n_adulti"],
|
path: ["n_adulti"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!permanenza && tipologia === TipologiaPosizioneEnum.Transitorio) {
|
|
||||||
refinementContext.issues.push({
|
|
||||||
code: "custom",
|
|
||||||
input: "",
|
|
||||||
message: "Inserisci permanenza",
|
|
||||||
path: ["permanenza"],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (tipologia === TipologiaPosizioneEnum.Transitorio) {
|
if (tipologia === TipologiaPosizioneEnum.Transitorio) {
|
||||||
if (
|
if (
|
||||||
motivazione_transitorio === "" ||
|
motivazione_transitorio === "" ||
|
||||||
|
|
@ -254,7 +248,7 @@ export const FormNewServizioAcquisto = ({
|
||||||
path: ["scadenza_motivazione_transitoria"],
|
path: ["scadenza_motivazione_transitoria"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!permanenza) {
|
if (permanenza == null) {
|
||||||
refinementContext.issues.push({
|
refinementContext.issues.push({
|
||||||
code: "custom",
|
code: "custom",
|
||||||
input: "",
|
input: "",
|
||||||
|
|
@ -301,7 +295,7 @@ export const FormNewServizioAcquisto = ({
|
||||||
defaultValues: defaultValues,
|
defaultValues: defaultValues,
|
||||||
});
|
});
|
||||||
const DatePickerLocale = locale === "it" ? it : enUS;
|
const DatePickerLocale = locale === "it" ? it : enUS;
|
||||||
const { mutate } = api.servizio.submitOnboardForPurchase.useMutation({
|
const { mutate } = api.servizio.processServizioOnboard.useMutation({
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(`Errore invio dati: ${error.message}`, {
|
toast.error(`Errore invio dati: ${error.message}`, {
|
||||||
id: "salvataggio",
|
id: "salvataggio",
|
||||||
|
|
@ -312,25 +306,19 @@ export const FormNewServizioAcquisto = ({
|
||||||
id: "salvataggio",
|
id: "salvataggio",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: async (data) => {
|
onSuccess: async () => {
|
||||||
toast.success("Servizio impostato con successo", {
|
toast.success("Servizio impostato con successo", {
|
||||||
id: "salvataggio",
|
id: "salvataggio",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
if (data.ordineIdForPayment) {
|
await router.push(
|
||||||
await router.push(`/area-riservata/admin/user-view/ordini/${userId}`);
|
`/area-riservata/admin/user-view/servizio/${userId}/${initialData.servizio.servizio_id}`,
|
||||||
} else {
|
);
|
||||||
await router.push(
|
|
||||||
`/area-riservata/admin/user-view/servizio/${userId}/${initialData.servizio.servizio_id}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (data.ordineIdForPayment) {
|
await router.push(
|
||||||
await router.push(`/servizio/pagamento/${data.ordineIdForPayment}`);
|
`/area-riservata/servizio/${initialData.servizio.servizio_id}`,
|
||||||
} else {
|
);
|
||||||
await router.push("/area-riservata/dashboard");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -1245,33 +1233,6 @@ export const FormNewServizioAcquisto = ({
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="flex w-full flex-wrap items-center justify-center gap-2 pb-8 sm:justify-end">
|
<div className="flex w-full flex-wrap items-center justify-center gap-2 pb-8 sm:justify-end">
|
||||||
{isAdmin && (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="skipPayment"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="py-2">
|
|
||||||
<div className="flex items-center justify-start gap-x-4">
|
|
||||||
<FormLabel htmlFor="skipPayment">
|
|
||||||
Vuoi saltare la creazione dell'ordine e impostare il
|
|
||||||
servizio come attivo?
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
className="data-[state=checked]:bg-neutral-700"
|
|
||||||
defaultChecked={field.value}
|
|
||||||
id="skipPayment"
|
|
||||||
onCheckedChange={(v) => {
|
|
||||||
field.onChange(v);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button className="w-full sm:w-auto" type="submit">
|
<Button className="w-full sm:w-auto" type="submit">
|
||||||
{texts.salva}
|
{texts.salva}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,58 @@
|
||||||
export type PaymentType =
|
export const PAYMENT_TYPES = [
|
||||||
| "manual"
|
"manual",
|
||||||
| "acss_debit"
|
"acss_debit",
|
||||||
| "affirm"
|
"affirm",
|
||||||
| "afterpay_clearpay"
|
"afterpay_clearpay",
|
||||||
| "alipay"
|
"alipay",
|
||||||
| "alma"
|
"alma",
|
||||||
| "amazon_pay"
|
"amazon_pay",
|
||||||
| "au_becs_debit"
|
"au_becs_debit",
|
||||||
| "bacs_debit"
|
"bacs_debit",
|
||||||
| "bancontact"
|
"bancontact",
|
||||||
| "billie"
|
"billie",
|
||||||
| "blik"
|
"blik",
|
||||||
| "boleto"
|
"boleto",
|
||||||
| "card"
|
"card",
|
||||||
| "card_present"
|
"card_present",
|
||||||
| "cashapp"
|
"cashapp",
|
||||||
| "customer_balance"
|
"customer_balance",
|
||||||
| "eps"
|
"eps",
|
||||||
| "fpx"
|
"fpx",
|
||||||
| "giropay"
|
"giropay",
|
||||||
| "grabpay"
|
"grabpay",
|
||||||
| "ideal"
|
"ideal",
|
||||||
| "interac_present"
|
"interac_present",
|
||||||
| "kakao_pay"
|
"kakao_pay",
|
||||||
| "klarna"
|
"klarna",
|
||||||
| "konbini"
|
"konbini",
|
||||||
| "kr_card"
|
"kr_card",
|
||||||
| "link"
|
"link",
|
||||||
| "mobilepay"
|
"mobilepay",
|
||||||
| "multibanco"
|
"multibanco",
|
||||||
| "naver_pay"
|
"naver_pay",
|
||||||
| "nz_bank_account"
|
"nz_bank_account",
|
||||||
| "oxxo"
|
"oxxo",
|
||||||
| "p24"
|
"p24",
|
||||||
| "pay_by_bank"
|
"pay_by_bank",
|
||||||
| "payco"
|
"payco",
|
||||||
| "paynow"
|
"paynow",
|
||||||
| "paypal"
|
"paypal",
|
||||||
| "pix"
|
"pix",
|
||||||
| "promptpay"
|
"promptpay",
|
||||||
| "revolut_pay"
|
"revolut_pay",
|
||||||
| "samsung_pay"
|
"samsung_pay",
|
||||||
| "satispay"
|
"satispay",
|
||||||
| "sepa_debit"
|
"sepa_debit",
|
||||||
| "sofort"
|
"sofort",
|
||||||
| "swish"
|
"swish",
|
||||||
| "twint"
|
"twint",
|
||||||
| "us_bank_account"
|
"us_bank_account",
|
||||||
| "wechat_pay"
|
"wechat_pay",
|
||||||
| "zip";
|
"zip",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type PaymentType = (typeof PAYMENT_TYPES)[number];
|
||||||
|
|
||||||
export const PaymentMethodToString = (type: PaymentType) => {
|
export const PaymentMethodToString = (type: PaymentType) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "manual":
|
case "manual":
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
className="w-full"
|
className="w-full"
|
||||||
href={`/area-riservata/admin/scheda-annuncio-stampa/${data.id}`}
|
href={`/area-riservata/scheda-annuncio-stampa/${data.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import type Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import stripe from "~/lib/stripe";
|
import stripe from "~/lib/stripe";
|
||||||
import type { PaymentsId } from "~/schemas/public/Payments";
|
|
||||||
import { appRouter } from "~/server/api/root";
|
import { appRouter } from "~/server/api/root";
|
||||||
import { addApiAuthToContext, createTRPCContext, t } from "~/server/api/trpc";
|
import { addApiAuthToContext, createTRPCContext, t } from "~/server/api/trpc";
|
||||||
|
import { zOrdineId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
const handler = async (
|
const handler = async (
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
|
|
@ -43,19 +43,16 @@ const handler = async (
|
||||||
const { id, metadata } = paymentIntent;
|
const { id, metadata } = paymentIntent;
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
res.status(400).send("No metadata in payment intent");
|
throw new Error("No metadata in payment intent");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (
|
const metadataOrdineId = metadata.ordineId;
|
||||||
!metadata.paymentId ||
|
const ordineId = zOrdineId.safeParse(metadataOrdineId);
|
||||||
!(typeof metadata.paymentId === "string")
|
if (!metadata.ordineId || !ordineId.success) {
|
||||||
) {
|
throw new Error("No ordineId or userId in metadata");
|
||||||
res.status(400).send("No orderId or userId in metadata");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await caller.stripe.whIntentCreated({
|
await caller.stripe.whIntentCreated({
|
||||||
paymentId: metadata.paymentId as PaymentsId,
|
ordineId: ordineId.data,
|
||||||
pIntentId: id,
|
pIntentId: id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -66,18 +63,15 @@ const handler = async (
|
||||||
const { id, metadata } = paymentIntent;
|
const { id, metadata } = paymentIntent;
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
res.status(400).send("No metadata in payment intent");
|
throw new Error("No metadata in payment intent");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (
|
const metadataOrdineId = metadata.ordineId;
|
||||||
!metadata.paymentId ||
|
const ordineId = zOrdineId.safeParse(metadataOrdineId);
|
||||||
!(typeof metadata.paymentId === "string")
|
if (!metadata.ordineId || !ordineId.success) {
|
||||||
) {
|
throw new Error("No ordineId or userId in metadata");
|
||||||
res.status(400).send("No orderId or userId in metadata");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
await caller.stripe.whIntentSucceeded({
|
await caller.stripe.whIntentSucceeded({
|
||||||
paymentId: metadata.paymentId as PaymentsId,
|
ordineId: ordineId.data,
|
||||||
pIntentId: id,
|
pIntentId: id,
|
||||||
pm_id:
|
pm_id:
|
||||||
typeof paymentIntent.payment_method === "string"
|
typeof paymentIntent.payment_method === "string"
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ const Ordini: NextPageWithLayout = () => {
|
||||||
<h3 className="font-bold text-2xl">Ordini</h3>
|
<h3 className="font-bold text-2xl">Ordini</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<OrdiniTable data={data} isAdmin />
|
<OrdiniTable data={data} filter={null} isAdmin />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ const OnboardUser: NextPageWithLayout<OnboardUserProps> = ({
|
||||||
<CatastoProvider>
|
<CatastoProvider>
|
||||||
<FormNewServizioAcquisto
|
<FormNewServizioAcquisto
|
||||||
initialData={data}
|
initialData={data}
|
||||||
isAdmin
|
isAdmin={true}
|
||||||
userId={data.userData.id}
|
userId={data.userData.id}
|
||||||
/>
|
/>
|
||||||
</CatastoProvider>
|
</CatastoProvider>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
import type { GetServerSideProps } from "next";
|
import type { GetServerSideProps } from "next";
|
||||||
import Link from "next/link";
|
import Head from "next/head";
|
||||||
import { OrdineManualeDialog } from "~/components/area-riservata/ordine_manuale";
|
|
||||||
import { AreaRiservataLayoutUserView } from "~/components/Layout";
|
import { AreaRiservataLayoutUserView } from "~/components/Layout";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { Status500 } from "~/components/status-page";
|
import { Status500 } from "~/components/status-page";
|
||||||
import { OrdiniTable } from "~/components/tables/orders-table";
|
import { OrdiniTable } from "~/components/tables/orders-table";
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
import { zServizioId, zUserId } from "~/server/utils/zod_types";
|
import { zUserId } from "~/server/utils/zod_types";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
type OrdiniUserProps = {
|
type OrdiniUserProps = {
|
||||||
userId: UsersId;
|
userId: UsersId;
|
||||||
servizioId: ServizioServizioId | null;
|
ordineId: OrdiniOrdineId | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const OrdiniUser: NextPageWithLayout<OrdiniUserProps> = ({
|
const OrdiniUser: NextPageWithLayout<OrdiniUserProps> = ({
|
||||||
userId,
|
userId,
|
||||||
servizioId,
|
ordineId,
|
||||||
}: OrdiniUserProps) => {
|
}: OrdiniUserProps) => {
|
||||||
const { data, isLoading } = api.servizio.getOrdini.useQuery({
|
const { data, isLoading } = api.servizio.getOrdini.useQuery({
|
||||||
userId,
|
userId,
|
||||||
|
|
@ -29,39 +27,24 @@ const OrdiniUser: NextPageWithLayout<OrdiniUserProps> = ({
|
||||||
if (isLoading) return <LoadingPage />;
|
if (isLoading) return <LoadingPage />;
|
||||||
if (!data) return <Status500 />;
|
if (!data) return <Status500 />;
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-1 flex-col items-start gap-4">
|
<>
|
||||||
<div className="flex w-full flex-wrap items-center justify-between gap-2">
|
<Head>
|
||||||
<h3 className="flex items-center gap-2 font-bold text-2xl">
|
<title>Ordini - Infoalloggi.it</title>
|
||||||
Ordini{" "}
|
</Head>
|
||||||
<span className="text-sm">
|
<main className="flex flex-1 flex-col items-start gap-4">
|
||||||
{servizioId ? `- Servizio ${servizioId}` : ""}
|
<div className="flex w-full flex-wrap items-center justify-between gap-2">
|
||||||
</span>
|
<h3 className="flex items-center gap-2 font-bold text-2xl">Ordini</h3>
|
||||||
</h3>
|
</div>
|
||||||
{servizioId && (
|
<OrdiniTable data={data} filter={ordineId} isAdmin userId={userId} />
|
||||||
<div className="flex items-center gap-2">
|
</main>
|
||||||
<OrdineManualeDialog servizioId={servizioId} userId={userId} />
|
</>
|
||||||
<Link href={`/area-riservata/admin/user-view/ordini/${userId}`}>
|
|
||||||
<Button size="sm" variant="destructive">
|
|
||||||
Rimuovi filtro
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<OrdiniTable
|
|
||||||
data={data.filter((ordine) =>
|
|
||||||
servizioId ? ordine.servizio_id === servizioId : true,
|
|
||||||
)}
|
|
||||||
isAdmin
|
|
||||||
userId={userId}
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OrdiniUser;
|
export default OrdiniUser;
|
||||||
export const getServerSideProps = (async (context) => {
|
export const getServerSideProps = (async (context) => {
|
||||||
const userId = context.params?.userId;
|
const userId = context.params?.userId;
|
||||||
|
|
||||||
if (typeof userId !== "string") {
|
if (typeof userId !== "string") {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
|
|
@ -75,6 +58,13 @@ export const getServerSideProps = (async (context) => {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const ordineId = context.query?.ordineId;
|
||||||
|
if (ordineId && typeof ordineId !== "string") {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const access_token = context.req.cookies.access_token;
|
const access_token = context.req.cookies.access_token;
|
||||||
|
|
||||||
const helpers = await TrpcAuthedFetchingIstance({ access_token });
|
const helpers = await TrpcAuthedFetchingIstance({ access_token });
|
||||||
|
|
@ -83,18 +73,11 @@ export const getServerSideProps = (async (context) => {
|
||||||
userId: parsed.data,
|
userId: parsed.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
let servizioId: ServizioServizioId | null = null;
|
|
||||||
|
|
||||||
const parsedServizioId = zServizioId.safeParse(context.query?.servizioId);
|
|
||||||
if (parsedServizioId.success) {
|
|
||||||
servizioId = parsedServizioId.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
ordineId: ordineId ? (ordineId as OrdiniOrdineId) : null,
|
||||||
userId: parsed.data,
|
userId: parsed.data,
|
||||||
trpcState: helpers.trpc.dehydrate(),
|
trpcState: helpers.trpc.dehydrate(),
|
||||||
servizioId,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,42 @@ import { Status500 } from "~/components/status-page";
|
||||||
import { OrdiniTable } from "~/components/tables/orders-table";
|
import { OrdiniTable } from "~/components/tables/orders-table";
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import { useEnforcedSession } from "~/providers/SessionProvider";
|
import { useEnforcedSession } from "~/providers/SessionProvider";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
const Ordini: NextPageWithLayout = () => {
|
type OrdiniProps = {
|
||||||
|
ordineId: OrdiniOrdineId | null;
|
||||||
|
};
|
||||||
|
const Ordini: NextPageWithLayout<OrdiniProps> = ({ ordineId }: OrdiniProps) => {
|
||||||
const session = useEnforcedSession();
|
const session = useEnforcedSession();
|
||||||
|
const { data, isLoading } = api.servizio.getOrdini.useQuery({
|
||||||
|
userId: session.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) return <LoadingPage />;
|
||||||
|
if (!data) return <Status500 />;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Ordini - Infoalloggi.it</title>
|
<title>Ordini - Infoalloggi.it</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SessionWrapper userId={session.user.id} />
|
<main className="mx-1 mt-2 flex flex-1 flex-col items-start gap-4 p-2 md:gap-6">
|
||||||
|
<div className="grid w-full auto-rows-max items-start gap-2 md:gap-4">
|
||||||
|
<div className="overflow-auto border-none">
|
||||||
|
<h3 className="font-bold text-2xl">Ordini</h3>
|
||||||
|
|
||||||
|
<div className="w-full py-4">
|
||||||
|
<OrdiniTable
|
||||||
|
data={data}
|
||||||
|
filter={ordineId}
|
||||||
|
isAdmin={false}
|
||||||
|
userId={session.user.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -25,12 +49,19 @@ export default Ordini;
|
||||||
|
|
||||||
export const getServerSideProps = (async (context) => {
|
export const getServerSideProps = (async (context) => {
|
||||||
const access_token = context.req.cookies.access_token;
|
const access_token = context.req.cookies.access_token;
|
||||||
|
const ordineId = context.query?.ordineId;
|
||||||
|
if (ordineId && typeof ordineId !== "string") {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const helpers = await TrpcAuthedFetchingIstance({ access_token });
|
const helpers = await TrpcAuthedFetchingIstance({ access_token });
|
||||||
if (helpers) {
|
if (helpers) {
|
||||||
await helpers.trpc.servizio.getOrdini.prefetch({});
|
await helpers.trpc.servizio.getOrdini.prefetch({});
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
ordineId: ordineId ? (ordineId as OrdiniOrdineId) : null,
|
||||||
trpcState: helpers.trpc.dehydrate(),
|
trpcState: helpers.trpc.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -40,29 +71,6 @@ export const getServerSideProps = (async (context) => {
|
||||||
};
|
};
|
||||||
}) satisfies GetServerSideProps;
|
}) satisfies GetServerSideProps;
|
||||||
|
|
||||||
const SessionWrapper = ({ userId }: { userId: UsersId }) => {
|
|
||||||
const { data, isLoading } = api.servizio.getOrdini.useQuery({
|
|
||||||
userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isLoading) return <LoadingPage />;
|
|
||||||
if (!data) return <Status500 />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="mx-1 mt-2 flex flex-1 flex-col items-start gap-4 p-2 md:gap-6">
|
|
||||||
<div className="grid w-full auto-rows-max items-start gap-2 md:gap-4">
|
|
||||||
<div className="overflow-auto border-none">
|
|
||||||
<h3 className="font-bold text-2xl">Ordini</h3>
|
|
||||||
|
|
||||||
<div className="w-full py-4">
|
|
||||||
<OrdiniTable data={data} isAdmin={false} userId={userId} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Ordini.getLayout = function getLayout(page) {
|
Ordini.getLayout = function getLayout(page) {
|
||||||
return <AreaRiservataLayout>{page}</AreaRiservataLayout>;
|
return <AreaRiservataLayout>{page}</AreaRiservataLayout>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
import { ArrowRight } from "lucide-react";
|
|
||||||
import type { GetServerSideProps } from "next";
|
import type { GetServerSideProps } from "next";
|
||||||
import Link from "next/link";
|
|
||||||
import { UserDashboardFaq } from "~/components/area-riservata/dashboard";
|
import { UserDashboardFaq } from "~/components/area-riservata/dashboard";
|
||||||
import { AreaRiservataLayout } from "~/components/Layout";
|
import { AreaRiservataLayout } from "~/components/Layout";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { Servizio } from "~/components/servizio/servizio";
|
import { Servizio } from "~/components/servizio/servizio";
|
||||||
import { Status500 } from "~/components/status-page";
|
import { Status500 } from "~/components/status-page";
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { Separator } from "~/components/ui/separator";
|
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
|
||||||
import { useEnforcedSession } from "~/providers/SessionProvider";
|
import { useEnforcedSession } from "~/providers/SessionProvider";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
|
|
@ -23,7 +18,6 @@ const ServizioPage: NextPageWithLayout<ServizioPageProps> = ({
|
||||||
servizioId,
|
servizioId,
|
||||||
}: ServizioPageProps) => {
|
}: ServizioPageProps) => {
|
||||||
const { user } = useEnforcedSession();
|
const { user } = useEnforcedSession();
|
||||||
const { t } = useTranslation();
|
|
||||||
const { data, isLoading } = api.servizio.getServizioData.useQuery({
|
const { data, isLoading } = api.servizio.getServizioData.useQuery({
|
||||||
servizioId,
|
servizioId,
|
||||||
});
|
});
|
||||||
|
|
@ -36,7 +30,7 @@ const ServizioPage: NextPageWithLayout<ServizioPageProps> = ({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-1 flex-col items-start justify-center gap-3 overflow-auto p-2 md:gap-6">
|
<div className="flex w-full flex-1 flex-col items-start justify-center gap-3 overflow-auto p-2 md:gap-6">
|
||||||
<div className="flex w-full flex-col gap-2">
|
{/* <div className=" w-full flex-col gap-2 flex">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<h3 className="font-semibold text-xl">
|
<h3 className="font-semibold text-xl">
|
||||||
{t.servizio.dettaglio_servizio}
|
{t.servizio.dettaglio_servizio}
|
||||||
|
|
@ -50,7 +44,7 @@ const ServizioPage: NextPageWithLayout<ServizioPageProps> = ({
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
</div>
|
</div> */}
|
||||||
<div className="flex w-full flex-1 grow flex-col space-y-5">
|
<div className="flex w-full flex-1 grow flex-col space-y-5">
|
||||||
<Servizio
|
<Servizio
|
||||||
isAdmin={false}
|
isAdmin={false}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,10 @@ const FAQSection = () => {
|
||||||
{item.qas.map((f, i) => (
|
{item.qas.map((f, i) => (
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
className="border-b dark:border-muted-foreground/50"
|
className="border-b dark:border-muted-foreground/50"
|
||||||
key={`accordion-item-${item.id}-${i}`}
|
key={`accordion-item-${item.id}-${
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: <ok>
|
||||||
|
i
|
||||||
|
}`}
|
||||||
value={i.toString()}
|
value={i.toString()}
|
||||||
>
|
>
|
||||||
<AccordionTrigger className="cursor-pointer text-left">
|
<AccordionTrigger className="cursor-pointer text-left">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { ArrowRight } from "lucide-react";
|
||||||
|
import type { GetServerSideProps } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { AreaRiservataLayout } from "~/components/Layout";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { formatCurrency } from "~/lib/utils";
|
||||||
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
|
import { zOrdineId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
|
type BonificoManualeProps = {
|
||||||
|
titolo: string;
|
||||||
|
prezzo: number;
|
||||||
|
sconto: number;
|
||||||
|
descrizione: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /servizio/bonifico-checkout/[ordineId] \
|
||||||
|
* Pagina di checkout per il pagamento di un ordine di servizio tramite bonifico
|
||||||
|
*/
|
||||||
|
const BonificoManualePage: NextPageWithLayout<BonificoManualeProps> = ({
|
||||||
|
titolo,
|
||||||
|
prezzo,
|
||||||
|
sconto,
|
||||||
|
descrizione,
|
||||||
|
}: BonificoManualeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto my-5 w-full max-w-lg space-y-8">
|
||||||
|
<h1 className="font-semibold text-3xl">Pagamento tramite bonifico</h1>
|
||||||
|
<div className="space-y-4 rounded border p-4">
|
||||||
|
<p className="font-bold text-xl">{titolo}</p>
|
||||||
|
<p className="text-lg">{descrizione}</p>
|
||||||
|
<p className="font-semibold text-lg">
|
||||||
|
Importo: {formatCurrency(prezzo / 100)}{" "}
|
||||||
|
{sconto > 0 && (
|
||||||
|
<span className="text-green-500">(Sconto: {sconto}%)</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2 rounded bg-gray-100 p-4">
|
||||||
|
<p className="font-semibold">Istruzioni per il bonifico:</p>
|
||||||
|
<ul className="list-disc pl-5">
|
||||||
|
<li>
|
||||||
|
Intestazione: <b>Arcenia Srl</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
IBAN: <b>IT33F0326860160052727620300</b> BANCA SELLA (Codice
|
||||||
|
Swift) SELBIT2BXXX per bonifici esteri
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Causale: <b>{titolo}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p className="">
|
||||||
|
Inoltraci la contabile del bonifico per velocizzare la verifica del
|
||||||
|
pagamento.
|
||||||
|
</p>
|
||||||
|
<span>Grazie!</span>
|
||||||
|
</div>
|
||||||
|
<Link href="/area-riservata/dashboard">
|
||||||
|
<Button className="w-full" variant="outline">
|
||||||
|
<span>Torna ai servizi</span>
|
||||||
|
<ArrowRight className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
BonificoManualePage.getLayout = function getLayout(page) {
|
||||||
|
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BonificoManualePage;
|
||||||
|
|
||||||
|
export const getServerSideProps = (async (context) => {
|
||||||
|
const id = context.params?.ordineId;
|
||||||
|
if (typeof id !== "string") {
|
||||||
|
console.error("Error: ordineId is not a string");
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/500",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = zOrdineId.safeParse(id);
|
||||||
|
if (!parsed.success) {
|
||||||
|
console.error("Error parsing ordineId", parsed.error);
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/500",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const access_token = context.req.cookies.access_token;
|
||||||
|
|
||||||
|
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
||||||
|
if (helper) {
|
||||||
|
const paymentData =
|
||||||
|
await helper.trpc.pagamenti.getPagamentoDataForCheckout.fetch({
|
||||||
|
ordineId: parsed.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
titolo: paymentData.nome_it,
|
||||||
|
prezzo: paymentData.amount_cent,
|
||||||
|
sconto: paymentData.sconto,
|
||||||
|
descrizione: paymentData.desc_it,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/500",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}) satisfies GetServerSideProps<BonificoManualeProps>;
|
||||||
|
|
@ -95,6 +95,7 @@ const ConfirmSection = ({
|
||||||
},
|
},
|
||||||
onSuccess: async (data, _variables, context) => {
|
onSuccess: async (data, _variables, context) => {
|
||||||
await utils.storage.retrieveUserFileData.invalidate();
|
await utils.storage.retrieveUserFileData.invalidate();
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
|
||||||
toast.success("Confermato", {
|
toast.success("Confermato", {
|
||||||
icon: "👍",
|
icon: "👍",
|
||||||
|
|
@ -105,7 +106,7 @@ const ConfirmSection = ({
|
||||||
`/area-riservata/admin/user-view/ricerca/${data.userId}`,
|
`/area-riservata/admin/user-view/ricerca/${data.userId}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await router.push("/area-riservata/dashboard");
|
await router.push(`/area-riservata/servizio/${servizioId}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,13 @@ import { api } from "~/utils/api";
|
||||||
type OnboardServizioProps = {
|
type OnboardServizioProps = {
|
||||||
servizioId: ServizioServizioId;
|
servizioId: ServizioServizioId;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* /servizio/onboard/[servizioId] \
|
||||||
|
* Pagina di onboarding per un nuovo servizio. Mostra un tutorial e un form per completare l'acquisto del servizio, con i dati precompilati se disponibili.
|
||||||
|
* Accessibile solo se l'onboarding è disponibile per l'utente e il servizio specificato.
|
||||||
|
* Se l'onboarding non è disponibile, reindirizza alla dashboard dell'area riservata.
|
||||||
|
* Se non è possibile recuperare i dati necessari, mostra una pagina di errore.
|
||||||
|
*/
|
||||||
const OnboardServizio: NextPageWithLayout<OnboardServizioProps> = ({
|
const OnboardServizio: NextPageWithLayout<OnboardServizioProps> = ({
|
||||||
servizioId,
|
servizioId,
|
||||||
}: OnboardServizioProps) => {
|
}: OnboardServizioProps) => {
|
||||||
|
|
@ -38,7 +44,11 @@ const OnboardServizio: NextPageWithLayout<OnboardServizioProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<OnboardTutorial />
|
<OnboardTutorial />
|
||||||
<CatastoProvider>
|
<CatastoProvider>
|
||||||
<FormNewServizioAcquisto initialData={data} userId={data.userData.id} />
|
<FormNewServizioAcquisto
|
||||||
|
initialData={data}
|
||||||
|
isAdmin={false}
|
||||||
|
userId={data.userData.id}
|
||||||
|
/>
|
||||||
</CatastoProvider>
|
</CatastoProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { AreaRiservataLayout } from "~/components/Layout";
|
||||||
import stripe from "~/lib/stripe";
|
import stripe from "~/lib/stripe";
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
import { zPagamentoId } from "~/server/utils/zod_types";
|
import { zOrdineId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
type PagamentoPageProps = {
|
type PagamentoPageProps = {
|
||||||
clientSecret: string | null;
|
clientSecret: string | null;
|
||||||
|
|
@ -13,6 +13,10 @@ type PagamentoPageProps = {
|
||||||
descrizione: string;
|
descrizione: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /servizio/pagamento-checkout/[ordineId] \
|
||||||
|
* Pagina di checkout per il pagamento di un ordine di servizio. Riceve l'ordineId, recupera i dati necessari e crea un PaymentIntent su Stripe, quindi passa il clientSecret al componente di checkout.
|
||||||
|
*/
|
||||||
const PagamentoPage: NextPageWithLayout<PagamentoPageProps> = ({
|
const PagamentoPage: NextPageWithLayout<PagamentoPageProps> = ({
|
||||||
clientSecret,
|
clientSecret,
|
||||||
titolo,
|
titolo,
|
||||||
|
|
@ -41,9 +45,9 @@ PagamentoPage.getLayout = function getLayout(page) {
|
||||||
export default PagamentoPage;
|
export default PagamentoPage;
|
||||||
|
|
||||||
export const getServerSideProps = (async (context) => {
|
export const getServerSideProps = (async (context) => {
|
||||||
const id = context.params?.paymentId;
|
const id = context.params?.ordineId;
|
||||||
if (typeof id !== "string") {
|
if (typeof id !== "string") {
|
||||||
console.error("Error: paymentId is not a string");
|
console.error("Error: ordineId is not a string");
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
destination: "/500",
|
destination: "/500",
|
||||||
|
|
@ -52,9 +56,9 @@ export const getServerSideProps = (async (context) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = zPagamentoId.safeParse(id);
|
const parsed = zOrdineId.safeParse(id);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
console.error("Error parsing paymentId", parsed.error);
|
console.error("Error parsing ordineId", parsed.error);
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
destination: "/500",
|
destination: "/500",
|
||||||
|
|
@ -69,9 +73,17 @@ export const getServerSideProps = (async (context) => {
|
||||||
if (helper) {
|
if (helper) {
|
||||||
const paymentData =
|
const paymentData =
|
||||||
await helper.trpc.pagamenti.getPagamentoDataForCheckout.fetch({
|
await helper.trpc.pagamenti.getPagamentoDataForCheckout.fetch({
|
||||||
paymentId: parsed.data,
|
ordineId: parsed.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const customer = await stripe.customers.create({
|
||||||
|
email: paymentData.email,
|
||||||
|
name: paymentData.username,
|
||||||
|
metadata: {
|
||||||
|
userId: paymentData.userid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { client_secret: clientSecret } = await stripe.paymentIntents.create({
|
const { client_secret: clientSecret } = await stripe.paymentIntents.create({
|
||||||
amount: paymentData.amount_cent,
|
amount: paymentData.amount_cent,
|
||||||
|
|
||||||
|
|
@ -79,12 +91,15 @@ export const getServerSideProps = (async (context) => {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
|
customer: customer.id,
|
||||||
description: paymentData.nome_it,
|
description: paymentData.nome_it,
|
||||||
metadata: {
|
metadata: {
|
||||||
paymentId: paymentData.id,
|
packId: paymentData.packid,
|
||||||
|
ordineId: paymentData.ordine_id,
|
||||||
servizioId: paymentData.servizio_id,
|
servizioId: paymentData.servizio_id,
|
||||||
userId: paymentData.userid,
|
userId: paymentData.userid,
|
||||||
},
|
},
|
||||||
|
|
||||||
receipt_email: paymentData.email,
|
receipt_email: paymentData.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -9,6 +9,7 @@ import stripe from "~/lib/stripe";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
||||||
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
|
|
||||||
enum AcquistoStatusEnum {
|
enum AcquistoStatusEnum {
|
||||||
|
|
@ -20,6 +21,7 @@ enum AcquistoStatusEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AcquistoProcessingProps = {
|
type AcquistoProcessingProps = {
|
||||||
|
servizioId: ServizioServizioId | null;
|
||||||
status: AcquistoStatusEnum;
|
status: AcquistoStatusEnum;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -38,6 +40,7 @@ export const getServerSideProps = (async (context) => {
|
||||||
if (!paymentIntent) {
|
if (!paymentIntent) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
servizioId: null,
|
||||||
status: AcquistoStatusEnum.internal_error,
|
status: AcquistoStatusEnum.internal_error,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -54,6 +57,7 @@ export const getServerSideProps = (async (context) => {
|
||||||
if (!payment) {
|
if (!payment) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
servizioId: null,
|
||||||
status: AcquistoStatusEnum.not_found,
|
status: AcquistoStatusEnum.not_found,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -75,6 +79,7 @@ export const getServerSideProps = (async (context) => {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
servizioId: payment.servizio_id,
|
||||||
status: resultStatus,
|
status: resultStatus,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -89,6 +94,7 @@ export const getServerSideProps = (async (context) => {
|
||||||
}) satisfies GetServerSideProps<AcquistoProcessingProps>;
|
}) satisfies GetServerSideProps<AcquistoProcessingProps>;
|
||||||
|
|
||||||
export default function AcquistoProcessing({
|
export default function AcquistoProcessing({
|
||||||
|
servizioId,
|
||||||
status,
|
status,
|
||||||
}: AcquistoProcessingProps) {
|
}: AcquistoProcessingProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -108,7 +114,7 @@ export default function AcquistoProcessing({
|
||||||
title={t.acquisto_elaborazione.success_title}
|
title={t.acquisto_elaborazione.success_title}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProgressRedirect href="/area-riservata/dashboard" />
|
<ProgressRedirect href={`/area-riservata/servizio/${servizioId}`} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{status === AcquistoStatusEnum.processing && (
|
{status === AcquistoStatusEnum.processing && (
|
||||||
|
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
import { ArrowRight, ExternalLink } from "lucide-react";
|
|
||||||
import type { GetServerSideProps } from "next";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { AreaRiservataLayout } from "~/components/Layout";
|
|
||||||
import { LoadingPage } from "~/components/loading";
|
|
||||||
import { Status500 } from "~/components/status-page";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { formatCurrency } from "~/lib/utils";
|
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
|
||||||
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
|
||||||
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
|
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
|
||||||
import { zOrdineId } from "~/server/utils/zod_types";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
|
|
||||||
type ServizioPurchaseProps = {
|
|
||||||
ordineId: OrdiniOrdineId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ServicePurchasePage: NextPageWithLayout<ServizioPurchaseProps> = ({
|
|
||||||
ordineId,
|
|
||||||
}: ServizioPurchaseProps) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const { data, isLoading } = api.servizio.getServizioPaymentData.useQuery({
|
|
||||||
ordineId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <LoadingPage />;
|
|
||||||
}
|
|
||||||
if (!data) {
|
|
||||||
return <Status500 />;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="mx-auto w-full max-w-6xl grow space-y-4 p-2 sm:px-8 sm:py-4">
|
|
||||||
<PricePreparation
|
|
||||||
onProcedi={async () =>
|
|
||||||
await router.push(`/servizio/pagamento-checkout/${data.payment.id}`)
|
|
||||||
}
|
|
||||||
packId={data.ordine.packid}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PricePreparation = ({
|
|
||||||
packId,
|
|
||||||
onProcedi,
|
|
||||||
}: {
|
|
||||||
packId: PrezziarioIdprezziario;
|
|
||||||
onProcedi: () => void;
|
|
||||||
}) => {
|
|
||||||
const { data: prezziario, isLoading } =
|
|
||||||
api.prezziario.getPrezzoPerServizio.useQuery({
|
|
||||||
idprezziario: packId,
|
|
||||||
});
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<div className="mx-auto mt-4 max-w-2xl p-2 sm:mt-10">
|
|
||||||
{isLoading || !prezziario ? (
|
|
||||||
<>
|
|
||||||
<h3 className="font-semibold text-2xl">{t.acquisto.preparazione}</h3>
|
|
||||||
<LoadingPage />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-8">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<p className="text-3xl">{prezziario.nome_it}</p>
|
|
||||||
<p className="text-lg">
|
|
||||||
Cod: “{prezziario.idprezziario}”
|
|
||||||
</p>
|
|
||||||
<p className="font-bold text-4xl">
|
|
||||||
{formatCurrency(prezziario.prezzo_cent / 100)}
|
|
||||||
</p>
|
|
||||||
<p>{prezziario.desc_it}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{prezziario.testo_condizioni && (
|
|
||||||
<Link
|
|
||||||
aria-label="Leggi le condizioni"
|
|
||||||
className="block w-fit"
|
|
||||||
href={`/servizio/condizioni/${prezziario.testo_condizioni}`}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<Button className="w-full text-sm">
|
|
||||||
<span>{t.acquisto.leggi_condizioni}</span>
|
|
||||||
<ExternalLink />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className="w-full text-lg"
|
|
||||||
disabled={isLoading || !prezziario}
|
|
||||||
onClick={onProcedi}
|
|
||||||
size="xl"
|
|
||||||
variant="info"
|
|
||||||
>
|
|
||||||
<span>{t.acquisto.procedi_pagamento}</span> <ArrowRight />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ServicePurchasePage.getLayout = function getLayout(page) {
|
|
||||||
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ServicePurchasePage;
|
|
||||||
|
|
||||||
export const getServerSideProps = (async (context) => {
|
|
||||||
const id = context.params?.ordineId;
|
|
||||||
|
|
||||||
if (typeof id !== "string") {
|
|
||||||
console.error("Error: ordineId is not a string");
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/500",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = zOrdineId.safeParse(id);
|
|
||||||
if (!parsed.success) {
|
|
||||||
console.error("Error parsing ordineId", parsed.error);
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/500",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const access_token = context.req.cookies.access_token;
|
|
||||||
|
|
||||||
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
|
||||||
if (helper) {
|
|
||||||
const isAwaitingPayment =
|
|
||||||
await helper.trpc.servizio.isOrdineAwaitingPayment.fetch({
|
|
||||||
ordineId: parsed.data,
|
|
||||||
});
|
|
||||||
if (!isAwaitingPayment) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/area-riservata/dashboard",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await helper.trpc.servizio.getServizioPaymentData.prefetch({
|
|
||||||
ordineId: parsed.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
ordineId: parsed.data,
|
|
||||||
trpcState: helper.trpc.dehydrate(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
destination: "/500",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}) satisfies GetServerSideProps<ServizioPurchaseProps>;
|
|
||||||
151
apps/infoalloggi/src/pages/servizio/pagamento/[servizioId].tsx
Normal file
151
apps/infoalloggi/src/pages/servizio/pagamento/[servizioId].tsx
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import { ArrowRight, ExternalLink } from "lucide-react";
|
||||||
|
import type { GetServerSideProps } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import z from "zod";
|
||||||
|
import { AreaRiservataLayout } from "~/components/Layout";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { formatCurrency } from "~/lib/utils";
|
||||||
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
|
import type { PreparedPaymentData } from "~/server/controllers/pagamenti.controller";
|
||||||
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
|
import { zServizioId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
|
type PreviewPurchaseProps = {
|
||||||
|
stripeDisabled: boolean;
|
||||||
|
data: PreparedPaymentData;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* /servizio/pagamento/[servizioId]?type=<OrderTypeEnum> \
|
||||||
|
* Pagina di preparazione al pagamento.
|
||||||
|
*/
|
||||||
|
const PreviewPurchasePage: NextPageWithLayout<PreviewPurchaseProps> = ({
|
||||||
|
stripeDisabled,
|
||||||
|
data,
|
||||||
|
}: PreviewPurchaseProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto w-full max-w-6xl grow space-y-4 p-2 sm:px-8 sm:py-4">
|
||||||
|
<div className="mx-auto mt-4 max-w-2xl p-2 sm:mt-10">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<p className="text-3xl">{data.packName}</p>
|
||||||
|
<p className="text-lg">Cod: “{data.packid}”</p>
|
||||||
|
<p className="font-bold text-4xl">
|
||||||
|
{formatCurrency(data.amount_cent / 100)} {data.sconto > 0 && <span className="text-green-500">(Sconto: {data.sconto}%)</span>}
|
||||||
|
</p>
|
||||||
|
<p>{data.packDesc}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
aria-label="Leggi le condizioni"
|
||||||
|
className="block w-fit"
|
||||||
|
href={`/servizio/condizioni/${data.condizioni}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<Button className="w-full text-sm">
|
||||||
|
<span>{t.acquisto.leggi_condizioni}</span>
|
||||||
|
<ExternalLink />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full text-lg"
|
||||||
|
onClick={async () =>
|
||||||
|
await router.push(
|
||||||
|
`/servizio/${stripeDisabled ? "bonifico-checkout" : "pagamento-checkout"}/${data.ordine_id}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="xl"
|
||||||
|
variant="info"
|
||||||
|
>
|
||||||
|
<span>{t.acquisto.procedi_pagamento}</span> <ArrowRight />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PreviewPurchasePage.getLayout = function getLayout(page) {
|
||||||
|
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PreviewPurchasePage;
|
||||||
|
|
||||||
|
export const getServerSideProps = (async (context) => {
|
||||||
|
const id = context.params?.servizioId;
|
||||||
|
|
||||||
|
if (typeof id !== "string") {
|
||||||
|
console.error("Error: servizioId is not a string");
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/500",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedServizioId = zServizioId.safeParse(id);
|
||||||
|
if (!parsedServizioId.success) {
|
||||||
|
console.error("Error parsing servizioId", parsedServizioId.error);
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/500",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const payment_type = context.query?.type;
|
||||||
|
if (typeof payment_type !== "string") {
|
||||||
|
console.error("Error: payment type is not a string");
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/500",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const parsedType = z.enum(OrderTypeEnum).safeParse(payment_type);
|
||||||
|
if (!parsedType.success) {
|
||||||
|
console.error("Error parsing payment type", parsedType.error);
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/500",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const access_token = context.req.cookies.access_token;
|
||||||
|
|
||||||
|
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
||||||
|
if (helper) {
|
||||||
|
const data = await helper.trpc.pagamenti.preparePayment.fetch({
|
||||||
|
type: parsedType.data,
|
||||||
|
servizioId: parsedServizioId.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const flag = await helper.trpc.flags.GetFlagValue.fetch({
|
||||||
|
id: "STRIPE_DISABLED",
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
stripeDisabled: flag || false,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/500",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}) satisfies GetServerSideProps<PreviewPurchaseProps>;
|
||||||
|
|
@ -45,7 +45,6 @@ export const useServizio = () => {
|
||||||
type ServizioAnnuncioProviderProps = {
|
type ServizioAnnuncioProviderProps = {
|
||||||
data: ServizioData["annunci"][number];
|
data: ServizioData["annunci"][number];
|
||||||
servizioId: ServizioServizioId;
|
servizioId: ServizioServizioId;
|
||||||
userId: UsersId;
|
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -60,7 +59,6 @@ const ServizioAnnuncioContext =
|
||||||
export const ServizioAnnuncioProvider = ({
|
export const ServizioAnnuncioProvider = ({
|
||||||
data,
|
data,
|
||||||
servizioId,
|
servizioId,
|
||||||
userId,
|
|
||||||
children,
|
children,
|
||||||
}: ServizioAnnuncioProviderProps) => {
|
}: ServizioAnnuncioProviderProps) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
@ -70,10 +68,7 @@ export const ServizioAnnuncioProvider = ({
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
await utils.servizio.invalidate();
|
||||||
userId: userId,
|
|
||||||
});
|
|
||||||
await utils.servizio.getServizioData.invalidate({ servizioId });
|
|
||||||
|
|
||||||
toast.success("Dati salvati con successo");
|
toast.success("Dati salvati con successo");
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export const authProxy: ProxyFn = async (req: NextRequest) => {
|
||||||
const path = req.nextUrl.pathname;
|
const path = req.nextUrl.pathname;
|
||||||
|
|
||||||
const isOverridableRoute =
|
const isOverridableRoute =
|
||||||
path.startsWith("/area-riservata/admin/scheda-annuncio-stampa/") ||
|
path.startsWith("/area-riservata/scheda-annuncio-stampa/") ||
|
||||||
path.startsWith("/servizio/condizioni/") ||
|
path.startsWith("/servizio/condizioni/") ||
|
||||||
path.startsWith("/servizio/ricevuta-cortesia/");
|
path.startsWith("/servizio/ricevuta-cortesia/");
|
||||||
// permette a puppeteer di accedere alla pagina di generazione PDF bypassando l'autenticazione normale
|
// permette a puppeteer di accedere alla pagina di generazione PDF bypassando l'autenticazione normale
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import type { UsersId } from './Users';
|
||||||
import type { PrezziarioIdprezziario } from './Prezziario';
|
import type { PrezziarioIdprezziario } from './Prezziario';
|
||||||
import type { ServizioServizioId } from './Servizio';
|
import type { ServizioServizioId } from './Servizio';
|
||||||
import type { default as OrderTypeEnum } from './OrderTypeEnum';
|
import type { default as OrderTypeEnum } from './OrderTypeEnum';
|
||||||
|
import type { default as PaymentStatusEnum } from './PaymentStatusEnum';
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.ordini */
|
/** Identifier type for public.ordini */
|
||||||
|
|
@ -25,6 +26,18 @@ export default interface OrdiniTable {
|
||||||
type: ColumnType<OrderTypeEnum, OrderTypeEnum, OrderTypeEnum>;
|
type: ColumnType<OrderTypeEnum, OrderTypeEnum, OrderTypeEnum>;
|
||||||
|
|
||||||
isActive: ColumnType<boolean, boolean | undefined, boolean>;
|
isActive: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
|
amount_cent: ColumnType<number, number, number>;
|
||||||
|
|
||||||
|
paymentmethod: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
|
paid_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||||
|
|
||||||
|
intent_id: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
|
paymentstatus: ColumnType<PaymentStatusEnum | null, PaymentStatusEnum | null, PaymentStatusEnum | null>;
|
||||||
|
|
||||||
|
sconto: ColumnType<number, number | undefined, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Ordini = Selectable<OrdiniTable>;
|
export type Ordini = Selectable<OrdiniTable>;
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ export default interface ServizioTable {
|
||||||
skipControlloDoc: ColumnType<boolean, boolean | undefined, boolean>;
|
skipControlloDoc: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
skipDocMotivazione: ColumnType<boolean, boolean | undefined, boolean>;
|
skipDocMotivazione: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
|
onboardOk: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Servizio = Selectable<ServizioTable>;
|
export type Servizio = Selectable<ServizioTable>;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
import type OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||||
import { getPagamentoDataForCheckout } from "~/server/controllers/servizio.controller";
|
import {
|
||||||
|
getPagamentoDataForCheckout,
|
||||||
|
preparePayment,
|
||||||
|
} from "~/server/controllers/pagamenti.controller";
|
||||||
|
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { zPagamentoId } from "~/server/utils/zod_types";
|
import { genPdfRicevutaCortesia } from "~/server/services/puppeteer.service";
|
||||||
|
import { zOrdineId, zServizioId } from "~/server/utils/zod_types";
|
||||||
// Create a new ratelimiter, that allows 10 requests per 10 seconds
|
// Create a new ratelimiter, that allows 10 requests per 10 seconds
|
||||||
/*
|
/*
|
||||||
const ratelimit = new RateLimiterHandler({
|
const ratelimit = new RateLimiterHandler({
|
||||||
|
|
@ -18,24 +24,45 @@ export const pagamentiRouter = createTRPCRouter({
|
||||||
.input(z.object({ pIntentId: z.string() }))
|
.input(z.object({ pIntentId: z.string() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
return (
|
const ordine = await db
|
||||||
(await db
|
.selectFrom("ordini")
|
||||||
.selectFrom("payments")
|
.selectAll("ordini")
|
||||||
.selectAll("payments")
|
.where("ordini.intent_id", "=", input.pIntentId)
|
||||||
.where("payments.intent_id", "=", input.pIntentId)
|
.executeTakeFirst();
|
||||||
.executeTakeFirst()) || null
|
return ordine || null;
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: `Error retriving payment: ${(error as Error).message}`,
|
message: `Errore durante il recupero del pagamento: ${(error as Error).message}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
preparePayment: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
servizioId: zServizioId,
|
||||||
|
type: z.custom<OrderTypeEnum>(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
return await preparePayment(input.servizioId, input.type);
|
||||||
|
}),
|
||||||
|
|
||||||
getPagamentoDataForCheckout: protectedProcedure
|
getPagamentoDataForCheckout: protectedProcedure
|
||||||
.input(z.object({ paymentId: zPagamentoId }))
|
.input(z.object({ ordineId: zOrdineId }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getPagamentoDataForCheckout(input.paymentId);
|
return await getPagamentoDataForCheckout(input.ordineId);
|
||||||
|
}),
|
||||||
|
getRicevutaCortesiaPdf: protectedProcedure
|
||||||
|
.input(z.object({ ordineId: zOrdineId }))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
try {
|
||||||
|
return await genPdfRicevutaCortesia(input.ordineId);
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Errore nella generazione della ricevuta di cortesia: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export const prezziarioRouter = createTRPCRouter({
|
||||||
getPrezzoPerServizio: publicProcedure
|
getPrezzoPerServizio: publicProcedure
|
||||||
.input(z.object({ idprezziario: zPrezziarioId }))
|
.input(z.object({ idprezziario: zPrezziarioId }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getPrezziarioByIdHandler({ id: input.idprezziario });
|
return await getPrezziarioByIdHandler(input.idprezziario);
|
||||||
}),
|
}),
|
||||||
updatePrezziario: adminProcedure
|
updatePrezziario: adminProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { z } from "zod/v4";
|
import z from "zod";
|
||||||
import type { NewOrdini, OrdiniUpdate } from "~/schemas/public/Ordini";
|
import type { NewOrdini, OrdiniUpdate } from "~/schemas/public/Ordini";
|
||||||
import type { PaymentsUpdate } from "~/schemas/public/Payments";
|
|
||||||
import type { NewServizio, ServizioUpdate } from "~/schemas/public/Servizio";
|
import type { NewServizio, ServizioUpdate } from "~/schemas/public/Servizio";
|
||||||
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
|
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
|
||||||
import type { Users } from "~/schemas/public/Users";
|
import type { Users } from "~/schemas/public/Users";
|
||||||
|
|
@ -18,34 +17,27 @@ import {
|
||||||
removeOrder,
|
removeOrder,
|
||||||
updateOrder,
|
updateOrder,
|
||||||
} from "~/server/controllers/ordini.controller";
|
} from "~/server/controllers/ordini.controller";
|
||||||
|
import { getPacksPerServizio } from "~/server/controllers/pagamenti.controller";
|
||||||
import {
|
import {
|
||||||
addServizio,
|
addServizio,
|
||||||
addServizioAnnunci,
|
addServizioAnnunci,
|
||||||
adminUpdateConfermaStatus,
|
adminUpdateConfermaStatus,
|
||||||
|
attivaServizio_SaltaPagamentoAcconto,
|
||||||
deleteServizio,
|
deleteServizio,
|
||||||
deleteServizioAnnuncio,
|
deleteServizioAnnuncio,
|
||||||
getAllPagamenti,
|
|
||||||
getAllServizioAnnunci,
|
getAllServizioAnnunci,
|
||||||
getAnnunciAvailableToAdd,
|
getAnnunciAvailableToAdd,
|
||||||
getCompatibileAnnunci,
|
getCompatibileAnnunci,
|
||||||
getDataPerAcquisto,
|
getDataPerAcquisto,
|
||||||
getOrdineRicevutaData,
|
getOrdineRicevutaData,
|
||||||
getPacksPerServizio,
|
|
||||||
getServiziByUserId,
|
getServiziByUserId,
|
||||||
getServizioAnnuncio,
|
getServizioAnnuncio,
|
||||||
getServizioById,
|
getServizioById,
|
||||||
getServizioDataById,
|
getServizioDataById,
|
||||||
getServizioPagamentoData,
|
|
||||||
getUserPagamenti,
|
|
||||||
InteractionLogicTipologia,
|
InteractionLogicTipologia,
|
||||||
interruzioneServizio,
|
interruzioneServizio,
|
||||||
isOrdineAwaitingPayment,
|
processServizioOnboard,
|
||||||
postAcquistoDataHandler,
|
|
||||||
removePagamento,
|
|
||||||
SbloccaContatti,
|
SbloccaContatti,
|
||||||
setupSaldoConsulenzaServizio,
|
|
||||||
setupSaldoServizio,
|
|
||||||
updatePagamento,
|
|
||||||
updateServizio,
|
updateServizio,
|
||||||
updateServizioAnnuncio,
|
updateServizioAnnuncio,
|
||||||
userAccettaConferma,
|
userAccettaConferma,
|
||||||
|
|
@ -54,7 +46,6 @@ import {
|
||||||
import {
|
import {
|
||||||
zAnnuncioId,
|
zAnnuncioId,
|
||||||
zOrdineId,
|
zOrdineId,
|
||||||
zPagamentoId,
|
|
||||||
zServizioId,
|
zServizioId,
|
||||||
zUserId,
|
zUserId,
|
||||||
} from "~/server/utils/zod_types";
|
} from "~/server/utils/zod_types";
|
||||||
|
|
@ -116,9 +107,7 @@ export const servizioRouter = createTRPCRouter({
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getDataPerAcquisto(input.servizioId);
|
return await getDataPerAcquisto(input.servizioId);
|
||||||
}),
|
}),
|
||||||
getAllPagamenti: adminProcedure.query(async () => {
|
|
||||||
return await getAllPagamenti();
|
|
||||||
}),
|
|
||||||
getAllServizioAnnunci: protectedProcedure
|
getAllServizioAnnunci: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
@ -185,6 +174,7 @@ export const servizioRouter = createTRPCRouter({
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getOrdini(input.userId);
|
return await getOrdini(input.userId);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
isOnboardAvailable: protectedProcedure
|
isOnboardAvailable: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
@ -229,24 +219,6 @@ export const servizioRouter = createTRPCRouter({
|
||||||
servizioId: input.servizioId,
|
servizioId: input.servizioId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
getServizioPaymentData: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
ordineId: zOrdineId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getServizioPagamentoData(input.ordineId);
|
|
||||||
}),
|
|
||||||
getUserPagamenti: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
userId: zUserId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getUserPagamenti(input.userId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
getUserServizi: adminProcedure
|
getUserServizi: adminProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|
@ -266,17 +238,8 @@ export const servizioRouter = createTRPCRouter({
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await interruzioneServizio(input.servizioId);
|
return await interruzioneServizio(input.servizioId);
|
||||||
}),
|
}),
|
||||||
isOrdineAwaitingPayment: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
ordineId: zOrdineId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await isOrdineAwaitingPayment(input.ordineId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
submitOnboardForPurchase: protectedProcedure
|
processServizioOnboard: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
anagrafica: z.custom<UsersAnagraficaUpdate>(),
|
anagrafica: z.custom<UsersAnagraficaUpdate>(),
|
||||||
|
|
@ -289,10 +252,20 @@ export const servizioRouter = createTRPCRouter({
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await postAcquistoDataHandler({
|
return await processServizioOnboard({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
attivazioneSenzaPagamento: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
ordineId: zOrdineId,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
return await attivaServizio_SaltaPagamentoAcconto(input.ordineId);
|
||||||
|
}),
|
||||||
|
|
||||||
removeAnnuncioServizio: protectedProcedure
|
removeAnnuncioServizio: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
@ -312,42 +285,7 @@ export const servizioRouter = createTRPCRouter({
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await removeOrder(input.ordineId);
|
return await removeOrder(input.ordineId);
|
||||||
}),
|
}),
|
||||||
removePagamento: adminProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
pagamentoId: zPagamentoId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await removePagamento(input.pagamentoId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
setupSaldoConsulenzaServizio: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
annuncioId: zAnnuncioId,
|
|
||||||
servizioId: zServizioId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await setupSaldoConsulenzaServizio({
|
|
||||||
annuncioId: input.annuncioId,
|
|
||||||
servizioId: input.servizioId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
setupSaldoServizio: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
annuncioId: zAnnuncioId,
|
|
||||||
servizioId: zServizioId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await setupSaldoServizio({
|
|
||||||
annuncioId: input.annuncioId,
|
|
||||||
servizioId: input.servizioId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
getPacksSolution: protectedProcedure
|
getPacksSolution: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
@ -392,19 +330,7 @@ export const servizioRouter = createTRPCRouter({
|
||||||
ordineId: input.ordineId,
|
ordineId: input.ordineId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
updatePagamento: adminProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
data: z.custom<PaymentsUpdate>(),
|
|
||||||
pagamentoId: zPagamentoId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await updatePagamento({
|
|
||||||
data: input.data,
|
|
||||||
paymentId: input.pagamentoId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
updateServizio: protectedProcedure
|
updateServizio: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
|
||||||
|
|
@ -3,42 +3,30 @@ import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
protectedApiProcedure,
|
protectedApiProcedure,
|
||||||
protectedProcedure,
|
|
||||||
} from "~/server/api/trpc";
|
} from "~/server/api/trpc";
|
||||||
import {
|
import {
|
||||||
createIntentHandler,
|
|
||||||
stripeHealthCheck,
|
stripeHealthCheck,
|
||||||
whIntentCreatedHandler,
|
whIntentCreatedHandler,
|
||||||
whIntentFailedHandler,
|
whIntentFailedHandler,
|
||||||
whIntentSucceededHandler,
|
whIntentSucceededHandler,
|
||||||
} from "~/server/controllers/stripe.controller";
|
} from "~/server/controllers/stripe.controller";
|
||||||
import { zPagamentoId } from "~/server/utils/zod_types";
|
import { zOrdineId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
export const stripeRouter = createTRPCRouter({
|
export const stripeRouter = createTRPCRouter({
|
||||||
health: adminProcedure.query(async () => {
|
health: adminProcedure.query(async () => {
|
||||||
return await stripeHealthCheck();
|
return await stripeHealthCheck();
|
||||||
}),
|
}),
|
||||||
createIntent: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
paymentId: zPagamentoId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await createIntentHandler({
|
|
||||||
paymentId: input.paymentId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
whIntentCreated: protectedApiProcedure
|
whIntentCreated: protectedApiProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
paymentId: zPagamentoId,
|
ordineId: zOrdineId,
|
||||||
pIntentId: z.string(),
|
pIntentId: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await whIntentCreatedHandler({
|
return await whIntentCreatedHandler({
|
||||||
paymentId: input.paymentId,
|
ordineId: input.ordineId,
|
||||||
pIntentId: input.pIntentId,
|
pIntentId: input.pIntentId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
@ -53,14 +41,14 @@ export const stripeRouter = createTRPCRouter({
|
||||||
whIntentSucceeded: protectedApiProcedure
|
whIntentSucceeded: protectedApiProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
paymentId: zPagamentoId,
|
ordineId: zOrdineId,
|
||||||
pIntentId: z.string(),
|
pIntentId: z.string(),
|
||||||
pm_id: z.string(),
|
pm_id: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await whIntentSucceededHandler({
|
return await whIntentSucceededHandler({
|
||||||
paymentId: input.paymentId,
|
ordineId: input.ordineId,
|
||||||
pIntentId: input.pIntentId,
|
pIntentId: input.pIntentId,
|
||||||
pm_id: input.pm_id,
|
pm_id: input.pm_id,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,23 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { jsonArrayFrom } from "kysely/helpers/postgres";
|
|
||||||
import type {
|
import type {
|
||||||
NewOrdini,
|
NewOrdini,
|
||||||
|
Ordini,
|
||||||
OrdiniOrdineId,
|
OrdiniOrdineId,
|
||||||
OrdiniUpdate,
|
OrdiniUpdate,
|
||||||
} from "~/schemas/public/Ordini";
|
} from "~/schemas/public/Ordini";
|
||||||
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
import type { Users, UsersId } from "~/schemas/public/Users";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import { db } from "~/server/db";
|
||||||
import { db, type Querier } from "~/server/db";
|
|
||||||
|
|
||||||
export type OrdiniWPagamenti = Awaited<ReturnType<typeof getOrdini>>[number];
|
export type Ordini_w_Utente = Ordini & Pick<Users, "username">;
|
||||||
export const getOrdini = async (userId: UsersId | undefined) => {
|
export const getOrdini = async (
|
||||||
|
userId: UsersId | undefined,
|
||||||
|
): Promise<Ordini_w_Utente[]> => {
|
||||||
try {
|
try {
|
||||||
let qry = db
|
let qry = db
|
||||||
.selectFrom("ordini")
|
.selectFrom("ordini")
|
||||||
.selectAll("ordini")
|
.selectAll("ordini")
|
||||||
.innerJoin("users", "users.id", "ordini.userid")
|
.innerJoin("users", "users.id", "ordini.userid")
|
||||||
.select("users.username")
|
.select("users.username");
|
||||||
.select((eb) => [
|
|
||||||
jsonArrayFrom(
|
|
||||||
eb
|
|
||||||
.selectFrom("payments")
|
|
||||||
.selectAll("payments")
|
|
||||||
.whereRef("payments.ordine_id", "=", "ordini.ordine_id")
|
|
||||||
.orderBy("payments.created_at", "desc"),
|
|
||||||
).as("pagamenti"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
qry = qry.where("ordini.userid", "=", userId);
|
qry = qry.where("ordini.userid", "=", userId);
|
||||||
|
|
@ -46,15 +38,6 @@ export const getOrdineById = async (ordineId: OrdiniOrdineId) => {
|
||||||
.selectAll("ordini")
|
.selectAll("ordini")
|
||||||
.innerJoin("users", "users.id", "ordini.userid")
|
.innerJoin("users", "users.id", "ordini.userid")
|
||||||
.select("users.username")
|
.select("users.username")
|
||||||
.select((eb) => [
|
|
||||||
jsonArrayFrom(
|
|
||||||
eb
|
|
||||||
.selectFrom("payments")
|
|
||||||
.selectAll("payments")
|
|
||||||
.whereRef("payments.ordine_id", "=", "ordini.ordine_id")
|
|
||||||
.orderBy("payments.created_at", "desc"),
|
|
||||||
).as("pagamenti"),
|
|
||||||
])
|
|
||||||
.where("ordini.ordine_id", "=", ordineId)
|
.where("ordini.ordine_id", "=", ordineId)
|
||||||
.orderBy("created_at", "desc")
|
.orderBy("created_at", "desc")
|
||||||
.executeTakeFirstOrThrow(
|
.executeTakeFirstOrThrow(
|
||||||
|
|
@ -70,38 +53,11 @@ export const getOrdineById = async (ordineId: OrdiniOrdineId) => {
|
||||||
|
|
||||||
export const createOrdine = async (data: NewOrdini) => {
|
export const createOrdine = async (data: NewOrdini) => {
|
||||||
try {
|
try {
|
||||||
return await db.transaction().execute(async (tx) => {
|
return await db
|
||||||
const prezzo = await tx
|
.insertInto("ordini")
|
||||||
.selectFrom("prezziario")
|
.values(data)
|
||||||
.select(["prezzo_cent", "nome_it"])
|
.returningAll()
|
||||||
.where("prezziario.idprezziario", "=", data.packid)
|
.executeTakeFirstOrThrow(() => new Error("Failed to create ordine"));
|
||||||
.executeTakeFirstOrThrow(
|
|
||||||
() => new Error(`Prezziario not found - packid: ${data.packid}`),
|
|
||||||
);
|
|
||||||
|
|
||||||
const order = await tx
|
|
||||||
.insertInto("ordini")
|
|
||||||
.values(data)
|
|
||||||
.returningAll()
|
|
||||||
.executeTakeFirstOrThrow(() => new Error("Failed to create ordine"));
|
|
||||||
await tx
|
|
||||||
.insertInto("payments")
|
|
||||||
.values({
|
|
||||||
ordine_id: order.ordine_id,
|
|
||||||
servizio_id: order.servizio_id,
|
|
||||||
userid: order.userid,
|
|
||||||
amount_cent: prezzo.prezzo_cent,
|
|
||||||
paymentname: prezzo.nome_it,
|
|
||||||
paymentmethod: "manual",
|
|
||||||
paymentstatus: data.isActive
|
|
||||||
? PaymentStatusEnum.success
|
|
||||||
: PaymentStatusEnum.processing,
|
|
||||||
paid_at: data.isActive ? new Date() : null,
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return order;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
@ -146,29 +102,3 @@ export const updateOrder = async ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanupUnusedOrders = async ({ db }: { db: Querier }) => {
|
|
||||||
try {
|
|
||||||
const orders = await db
|
|
||||||
.selectFrom("ordini")
|
|
||||||
.select("ordini.ordine_id")
|
|
||||||
.leftJoin("payments", "payments.ordine_id", "ordini.ordine_id")
|
|
||||||
.where("payments.paymentstatus", "is", null)
|
|
||||||
.where("ordini.isActive", "=", false)
|
|
||||||
.distinct()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
if (orders.length === 0) {
|
|
||||||
return "ok";
|
|
||||||
}
|
|
||||||
const ordineIds = orders.map((o) => o.ordine_id);
|
|
||||||
await db.deleteFrom("ordini").where("ordine_id", "in", ordineIds).execute();
|
|
||||||
|
|
||||||
return "ok";
|
|
||||||
} catch (e) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: `Error cleaning up old orders: ${(e as Error).message}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
345
apps/infoalloggi/src/server/controllers/pagamenti.controller.ts
Normal file
345
apps/infoalloggi/src/server/controllers/pagamenti.controller.ts
Normal file
|
|
@ -0,0 +1,345 @@
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
|
import type {
|
||||||
|
Prezziario,
|
||||||
|
PrezziarioIdprezziario,
|
||||||
|
} from "~/schemas/public/Prezziario";
|
||||||
|
import type { Servizio, ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
|
import { db } from "../db";
|
||||||
|
import { getPrezziarioByIdHandler } from "../services/prezziario.service";
|
||||||
|
import { createOrdine } from "./ordini.controller";
|
||||||
|
import { getServizioById } from "./servizio.controller";
|
||||||
|
|
||||||
|
export const ACCONTO_TRANSITORIO = "ACCONTO_BD" as PrezziarioIdprezziario;
|
||||||
|
export const ACCONTO_STABILE = "ACCONTO_CA" as PrezziarioIdprezziario;
|
||||||
|
|
||||||
|
export type PreparedPaymentData = {
|
||||||
|
ordine_id: OrdiniOrdineId;
|
||||||
|
packid: PrezziarioIdprezziario;
|
||||||
|
amount_cent: number;
|
||||||
|
sconto: number;
|
||||||
|
packName: string;
|
||||||
|
packDesc: string;
|
||||||
|
condizioni: string;
|
||||||
|
};
|
||||||
|
export const preparePayment = async (
|
||||||
|
servizioId: ServizioServizioId,
|
||||||
|
type: OrderTypeEnum,
|
||||||
|
): Promise<PreparedPaymentData> => {
|
||||||
|
try {
|
||||||
|
const servizio = await getServizioById(servizioId);
|
||||||
|
const inactiveOrders = await db
|
||||||
|
.selectFrom("ordini")
|
||||||
|
.selectAll()
|
||||||
|
.where("servizio_id", "=", servizioId)
|
||||||
|
.where("isActive", "=", false)
|
||||||
|
.where("paymentstatus", "is", null)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case OrderTypeEnum.Acconto: {
|
||||||
|
if (!servizio.isOkAcconto) {
|
||||||
|
const acconto = await AccontoSolver(servizio);
|
||||||
|
if (!acconto.success) {
|
||||||
|
throw new Error(
|
||||||
|
`Acconto non trovato per il servizio: ${acconto.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!acconto.pack.testo_condizioni) {
|
||||||
|
throw new Error("Condizioni non trovate per acconto");
|
||||||
|
}
|
||||||
|
|
||||||
|
// servizio sta aspettando attivazione acconto
|
||||||
|
|
||||||
|
const preExistingAcconto = inactiveOrders.find(
|
||||||
|
(ordine) => ordine.type === OrderTypeEnum.Acconto,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (preExistingAcconto) {
|
||||||
|
// acconto già creato, procedere con questo
|
||||||
|
return {
|
||||||
|
ordine_id: preExistingAcconto.ordine_id,
|
||||||
|
packid: preExistingAcconto.packid,
|
||||||
|
amount_cent: preExistingAcconto.amount_cent,
|
||||||
|
sconto: preExistingAcconto.sconto,
|
||||||
|
packName: acconto.pack.nome_it,
|
||||||
|
packDesc: acconto.pack.desc_it,
|
||||||
|
condizioni: acconto.pack.testo_condizioni,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// nessun acconto preesistente, crearne uno nuovo
|
||||||
|
|
||||||
|
const ordine = await createOrdine({
|
||||||
|
servizio_id: servizioId,
|
||||||
|
amount_cent: acconto.pack.prezzo_cent,
|
||||||
|
packid: acconto.pack.idprezziario,
|
||||||
|
type: OrderTypeEnum.Acconto,
|
||||||
|
userid: servizio.user_id,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
ordine_id: ordine.ordine_id,
|
||||||
|
packid: acconto.pack.idprezziario,
|
||||||
|
amount_cent: acconto.pack.prezzo_cent,
|
||||||
|
sconto: 0,
|
||||||
|
packName: acconto.pack.nome_it,
|
||||||
|
packDesc: acconto.pack.desc_it,
|
||||||
|
condizioni: acconto.pack.testo_condizioni,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
"Il servizio ha già un acconto attivo, non è possibile preparare un nuovo pagamento",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case OrderTypeEnum.Saldo: {
|
||||||
|
if (!servizio.isOkAcconto) {
|
||||||
|
throw new Error(
|
||||||
|
"Non è possibile procedere con il pagamento del saldo prima di aver attivato l'acconto",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!servizio.isOkSaldo) {
|
||||||
|
//find saldo pack based on servizio tipologia and permanenza/budget, if tipologia posizione
|
||||||
|
const saldo = await SaldoSolver(servizio);
|
||||||
|
if (!saldo.success) {
|
||||||
|
throw new Error(
|
||||||
|
`Saldo non trovato per il servizio: ${saldo.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!saldo.pack.testo_condizioni) {
|
||||||
|
throw new Error("Condizioni non trovate per saldo");
|
||||||
|
}
|
||||||
|
const preExistingSaldo = inactiveOrders.find(
|
||||||
|
(ordine) => ordine.type === OrderTypeEnum.Saldo,
|
||||||
|
);
|
||||||
|
if (preExistingSaldo) {
|
||||||
|
// saldo già creato, procedere con questo
|
||||||
|
return {
|
||||||
|
ordine_id: preExistingSaldo.ordine_id,
|
||||||
|
packid: preExistingSaldo.packid,
|
||||||
|
amount_cent: preExistingSaldo.amount_cent,
|
||||||
|
sconto: preExistingSaldo.sconto,
|
||||||
|
packName: saldo.pack.nome_it,
|
||||||
|
packDesc: saldo.pack.desc_it,
|
||||||
|
condizioni: saldo.pack.testo_condizioni,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// nessun saldo preesistente, crearne uno nuovo
|
||||||
|
const ordine = await createOrdine({
|
||||||
|
servizio_id: servizioId,
|
||||||
|
amount_cent: saldo.pack.prezzo_cent,
|
||||||
|
packid: saldo.pack.idprezziario,
|
||||||
|
type: OrderTypeEnum.Saldo,
|
||||||
|
userid: servizio.user_id,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
ordine_id: ordine.ordine_id,
|
||||||
|
packid: saldo.pack.idprezziario,
|
||||||
|
amount_cent: saldo.pack.prezzo_cent,
|
||||||
|
sconto: 0,
|
||||||
|
packName: saldo.pack.nome_it,
|
||||||
|
packDesc: saldo.pack.desc_it,
|
||||||
|
condizioni: saldo.pack.testo_condizioni,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
"Il servizio ha già un saldo attivo, non è possibile preparare un nuovo pagamento",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case OrderTypeEnum.Consulenza: {
|
||||||
|
if (!servizio.isOkAcconto || !servizio.isOkSaldo) {
|
||||||
|
throw new Error(
|
||||||
|
"Non è possibile procedere con il pagamento della consulenza prima di aver attivato acconto e saldo",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!servizio.isOkConsulenza) {
|
||||||
|
const existingConsulenza = inactiveOrders.find(
|
||||||
|
(ordine) => ordine.type === OrderTypeEnum.Consulenza,
|
||||||
|
);
|
||||||
|
if (existingConsulenza) {
|
||||||
|
// consulenza già creato, procedere con questo
|
||||||
|
const consulenzaPack = await getPrezziarioByIdHandler(
|
||||||
|
existingConsulenza.packid,
|
||||||
|
);
|
||||||
|
if (!consulenzaPack) {
|
||||||
|
throw new Error("Pack consulenza non trovato");
|
||||||
|
}
|
||||||
|
if (!consulenzaPack.testo_condizioni) {
|
||||||
|
throw new Error("Condizioni non trovate per consulenza");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ordine_id: existingConsulenza.ordine_id,
|
||||||
|
packid: existingConsulenza.packid,
|
||||||
|
amount_cent: existingConsulenza.amount_cent,
|
||||||
|
sconto: existingConsulenza.sconto,
|
||||||
|
packName: consulenzaPack.nome_it,
|
||||||
|
packDesc: consulenzaPack.desc_it,
|
||||||
|
condizioni: consulenzaPack.testo_condizioni,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//todo vedere se oltre a emettere un errore si vuole anche passare un messaggio alla pagina di errore
|
||||||
|
throw new Error(
|
||||||
|
"Il servizio non ha un pacchetto di consulenza, è necessario inserirlo prima di procedere",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
"Il servizio ha già una consulenza attiva, non è possibile preparare un nuovo pagamento",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error("Tipo di ordine non valido");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Errore durante la preparazione del pagamento: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPagamentoDataForCheckout = async (ordineId: OrdiniOrdineId) => {
|
||||||
|
try {
|
||||||
|
return await db
|
||||||
|
.selectFrom("ordini")
|
||||||
|
.select([
|
||||||
|
"ordini.ordine_id",
|
||||||
|
"ordini.packid",
|
||||||
|
"ordini.amount_cent",
|
||||||
|
"ordini.servizio_id",
|
||||||
|
"ordini.userid",
|
||||||
|
"ordini.sconto",
|
||||||
|
])
|
||||||
|
.innerJoin("users", "users.id", "ordini.userid")
|
||||||
|
.select(["users.username", "users.email"])
|
||||||
|
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
||||||
|
.select(["prezziario.nome_it", "prezziario.desc_it"])
|
||||||
|
.where("ordini.ordine_id", "=", ordineId)
|
||||||
|
.executeTakeFirstOrThrow(
|
||||||
|
() => new Error(`Payment not found - ordineId: ${ordineId}`),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: `Payment not found: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SaldoTransitorioMapping: Record<number, PrezziarioIdprezziario> = {
|
||||||
|
0: "SALDOBD_1MESE" as PrezziarioIdprezziario,
|
||||||
|
1: "SALDOBD_3MESI" as PrezziarioIdprezziario,
|
||||||
|
2: "SALDOBD_6MESI" as PrezziarioIdprezziario,
|
||||||
|
3: "SALDOBD_9MESI" as PrezziarioIdprezziario,
|
||||||
|
4: "SALDOBD_12MESI" as PrezziarioIdprezziario,
|
||||||
|
};
|
||||||
|
|
||||||
|
// const ConsulenzaPriceMapping = {
|
||||||
|
// "4 + 4": "CONS_4+4" as PrezziarioIdprezziario,
|
||||||
|
// "30 Giorni": "CONS_30GG" as PrezziarioIdprezziario,
|
||||||
|
// Agevolato: "CONS_3+2" as PrezziarioIdprezziario,
|
||||||
|
// Commerciale: "CONS_COMM" as PrezziarioIdprezziario,
|
||||||
|
// Foresteria: "CONS_COMM" as PrezziarioIdprezziario,
|
||||||
|
// "Transi.Age": "CONS_3+2" as PrezziarioIdprezziario,
|
||||||
|
// Transitorio: "CONS_TRAN" as PrezziarioIdprezziario,
|
||||||
|
// };
|
||||||
|
|
||||||
|
type GetPackError = { success: false; message: string };
|
||||||
|
type GetPackResult = { success: true; pack: Prezziario };
|
||||||
|
|
||||||
|
type ServizioPacks = {
|
||||||
|
acconto: GetPackResult | GetPackError;
|
||||||
|
saldo: GetPackResult | GetPackError;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccontoSolver = async (
|
||||||
|
servizio: Servizio,
|
||||||
|
): Promise<GetPackResult | GetPackError> => {
|
||||||
|
switch (servizio.tipologia) {
|
||||||
|
case TipologiaPosizioneEnum.Transitorio: {
|
||||||
|
const pack = await getPrezziarioByIdHandler(ACCONTO_TRANSITORIO);
|
||||||
|
|
||||||
|
if (!pack) {
|
||||||
|
return { success: false, message: "Pack non trovato" };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
pack,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case TipologiaPosizioneEnum.Stabile: {
|
||||||
|
const pack = await getPrezziarioByIdHandler(ACCONTO_STABILE);
|
||||||
|
|
||||||
|
if (!pack) {
|
||||||
|
return { success: false, message: "Pack non trovato" };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
pack,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return { success: false, message: "Seleziona tipologia" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaldoSolver = async (
|
||||||
|
servizio: Servizio,
|
||||||
|
): Promise<GetPackResult | GetPackError> => {
|
||||||
|
switch (servizio.tipologia) {
|
||||||
|
case TipologiaPosizioneEnum.Transitorio: {
|
||||||
|
const mpp =
|
||||||
|
servizio.permanenza && SaldoTransitorioMapping[servizio.permanenza];
|
||||||
|
if (!servizio.permanenza || !mpp) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Permanenza non selezionata",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const pack = await getPrezziarioByIdHandler(mpp);
|
||||||
|
if (!pack) {
|
||||||
|
return { success: false, message: "Pack non trovato" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, pack };
|
||||||
|
}
|
||||||
|
case TipologiaPosizioneEnum.Stabile: {
|
||||||
|
const pack = await getPrezziarioByIdHandler(
|
||||||
|
(servizio.budget > 700
|
||||||
|
? "SALDOCA700+"
|
||||||
|
: servizio.budget > 500
|
||||||
|
? "SALDOCA500+"
|
||||||
|
: "SALDOCA500") as PrezziarioIdprezziario,
|
||||||
|
);
|
||||||
|
if (!pack) {
|
||||||
|
return { success: false, message: "Pack non trovato" };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
pack,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return { success: false, message: "Seleziona tipologia" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPacksPerServizio = async (
|
||||||
|
servizioId: ServizioServizioId,
|
||||||
|
): Promise<ServizioPacks> => {
|
||||||
|
const servizio = await getServizioById(servizioId);
|
||||||
|
if (!servizio) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Servizio non trovato",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
acconto: await AccontoSolver(servizio),
|
||||||
|
saldo: await SaldoSolver(servizio),
|
||||||
|
};
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,8 +2,8 @@ import { TRPCError } from "@trpc/server";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import stripe from "~/lib/stripe";
|
import stripe from "~/lib/stripe";
|
||||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
|
import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
||||||
import type { Payments, PaymentsId } from "~/schemas/public/Payments";
|
|
||||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
|
|
@ -31,71 +31,22 @@ export const stripeHealthCheck = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createIntentHandler = async ({
|
|
||||||
paymentId,
|
|
||||||
}: {
|
|
||||||
paymentId: PaymentsId;
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
const payment = await db
|
|
||||||
.selectFrom("payments")
|
|
||||||
.selectAll("payments")
|
|
||||||
.innerJoin("users", "users.id", "payments.userid")
|
|
||||||
.select("users.email")
|
|
||||||
.innerJoin("ordini", "ordini.ordine_id", "payments.ordine_id")
|
|
||||||
.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
|
|
||||||
.select(["prezziario.nome_it", "prezziario.desc_it"])
|
|
||||||
.where("payments.id", "=", paymentId)
|
|
||||||
.executeTakeFirstOrThrow(
|
|
||||||
() => new Error(`Payment not found - paymentId: ${paymentId}`),
|
|
||||||
);
|
|
||||||
|
|
||||||
const paymentIntent = await stripe.paymentIntents.create({
|
|
||||||
amount: payment.amount_cent,
|
|
||||||
//payment_method_configuration: "pmc_1P4NfjILe4KoQRqXYChFCTFj",
|
|
||||||
automatic_payment_methods: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
currency: "eur",
|
|
||||||
description: payment.nome_it,
|
|
||||||
metadata: {
|
|
||||||
paymentId,
|
|
||||||
servizioId: payment.servizio_id,
|
|
||||||
userId: payment.userid,
|
|
||||||
},
|
|
||||||
receipt_email: payment.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
clientSecret: paymentIntent.client_secret,
|
|
||||||
descrizione: payment.desc_it,
|
|
||||||
prezzo: payment.amount_cent,
|
|
||||||
titolo: payment.nome_it,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: `Error creating payment intent: ${(error as Error).message}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const whIntentCreatedHandler = async ({
|
export const whIntentCreatedHandler = async ({
|
||||||
pIntentId,
|
pIntentId,
|
||||||
paymentId,
|
ordineId,
|
||||||
}: {
|
}: {
|
||||||
pIntentId: Payments["intent_id"];
|
pIntentId: Ordini["intent_id"];
|
||||||
paymentId: PaymentsId;
|
ordineId: OrdiniOrdineId;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
return await db
|
return await db
|
||||||
.updateTable("payments")
|
.updateTable("ordini")
|
||||||
.set({
|
.set({
|
||||||
intent_id: pIntentId,
|
intent_id: pIntentId,
|
||||||
paymentstatus: PaymentStatusEnum.processing,
|
paymentstatus: PaymentStatusEnum.processing,
|
||||||
})
|
})
|
||||||
.where("id", "=", paymentId)
|
.where("ordine_id", "=", ordineId)
|
||||||
.returning("id")
|
.returning("ordine_id")
|
||||||
.execute();
|
.execute();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|
@ -107,56 +58,41 @@ export const whIntentCreatedHandler = async ({
|
||||||
|
|
||||||
export const whIntentSucceededHandler = async ({
|
export const whIntentSucceededHandler = async ({
|
||||||
pIntentId,
|
pIntentId,
|
||||||
paymentId,
|
ordineId,
|
||||||
pm_id,
|
pm_id,
|
||||||
}: {
|
}: {
|
||||||
pIntentId: Payments["intent_id"];
|
pIntentId: Ordini["intent_id"];
|
||||||
paymentId: PaymentsId;
|
ordineId: OrdiniOrdineId;
|
||||||
pm_id: string;
|
pm_id: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const paymentMethod = await stripe.paymentMethods.retrieve(pm_id);
|
const paymentMethod = await stripe.paymentMethods.retrieve(pm_id);
|
||||||
|
|
||||||
await db.transaction().execute(async (trx) => {
|
await db.transaction().execute(async (trx) => {
|
||||||
const payment = await trx
|
const ordine = await trx
|
||||||
.updateTable("payments")
|
.updateTable("ordini")
|
||||||
.set({
|
.set({
|
||||||
paid_at: new Date(),
|
paid_at: new Date(),
|
||||||
paymentmethod: paymentMethod.type,
|
paymentmethod: paymentMethod.type,
|
||||||
paymentstatus: PaymentStatusEnum.success,
|
paymentstatus: PaymentStatusEnum.success,
|
||||||
|
isActive: true,
|
||||||
})
|
})
|
||||||
.where("intent_id", "=", pIntentId)
|
.where("intent_id", "=", pIntentId)
|
||||||
.where("payments.id", "=", paymentId)
|
.where("ordine_id", "=", ordineId)
|
||||||
.returning(["servizio_id", "ordine_id"])
|
.returning(["servizio_id", "ordine_id", "userid", "type", "created_at"])
|
||||||
.executeTakeFirstOrThrow(() => {
|
.executeTakeFirstOrThrow(() => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Payment not found - intent_id: ${pIntentId}, paymentId: ${paymentId}`,
|
`Payment not found - intent_id: ${pIntentId}, ordineId: ${ordineId}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ordine = await trx
|
|
||||||
.selectFrom("ordini")
|
|
||||||
.selectAll("ordini")
|
|
||||||
.where("ordine_id", "=", payment.ordine_id)
|
|
||||||
.executeTakeFirstOrThrow(() => {
|
|
||||||
throw new Error(`Ordine not found - ordine_id: ${payment.ordine_id}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
await trx
|
|
||||||
.updateTable("ordini")
|
|
||||||
.where("ordine_id", "=", payment.ordine_id)
|
|
||||||
.set({
|
|
||||||
isActive: true,
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
const servizio = await trx
|
const servizio = await trx
|
||||||
.selectFrom("servizio")
|
.selectFrom("servizio")
|
||||||
.select("servizio.tipologia")
|
.select(["servizio.tipologia", "servizio.servizio_id"])
|
||||||
.where("servizio_id", "=", payment.servizio_id)
|
.where("servizio_id", "=", ordine.servizio_id)
|
||||||
.executeTakeFirstOrThrow(() => {
|
.executeTakeFirstOrThrow(() => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Servizio not found - servizio_id: ${payment.servizio_id}`,
|
`Servizio not found - servizio_id: ${ordine.servizio_id}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -170,7 +106,7 @@ export const whIntentSucceededHandler = async ({
|
||||||
case OrderTypeEnum.Acconto:
|
case OrderTypeEnum.Acconto:
|
||||||
await trx
|
await trx
|
||||||
.updateTable("servizio")
|
.updateTable("servizio")
|
||||||
.where("servizio_id", "=", payment.servizio_id)
|
.where("servizio_id", "=", servizio.servizio_id)
|
||||||
.set({
|
.set({
|
||||||
decorrenza: new Date(),
|
decorrenza: new Date(),
|
||||||
isOkAcconto: true,
|
isOkAcconto: true,
|
||||||
|
|
@ -223,7 +159,7 @@ export const whIntentSucceededHandler = async ({
|
||||||
case OrderTypeEnum.Saldo:
|
case OrderTypeEnum.Saldo:
|
||||||
await trx
|
await trx
|
||||||
.updateTable("servizio")
|
.updateTable("servizio")
|
||||||
.where("servizio_id", "=", payment.servizio_id)
|
.where("servizio_id", "=", servizio.servizio_id)
|
||||||
.set({
|
.set({
|
||||||
isOkConsulenza:
|
isOkConsulenza:
|
||||||
servizio.tipologia === TipologiaPosizioneEnum.Transitorio,
|
servizio.tipologia === TipologiaPosizioneEnum.Transitorio,
|
||||||
|
|
@ -263,7 +199,7 @@ export const whIntentSucceededHandler = async ({
|
||||||
case OrderTypeEnum.Consulenza:
|
case OrderTypeEnum.Consulenza:
|
||||||
await trx
|
await trx
|
||||||
.updateTable("servizio")
|
.updateTable("servizio")
|
||||||
.where("servizio_id", "=", payment.servizio_id)
|
.where("servizio_id", "=", servizio.servizio_id)
|
||||||
.set({
|
.set({
|
||||||
isOkConsulenza: true,
|
isOkConsulenza: true,
|
||||||
})
|
})
|
||||||
|
|
@ -336,7 +272,7 @@ export const whIntentSucceededHandler = async ({
|
||||||
// Enhanced error logging
|
// Enhanced error logging
|
||||||
console.error("whIntentSucceededHandler error:", {
|
console.error("whIntentSucceededHandler error:", {
|
||||||
pIntentId,
|
pIntentId,
|
||||||
paymentId,
|
ordineId,
|
||||||
pm_id,
|
pm_id,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
stack: error instanceof Error ? error.stack : undefined,
|
||||||
|
|
@ -352,10 +288,10 @@ export const whIntentSucceededHandler = async ({
|
||||||
export const whIntentFailedHandler = async ({
|
export const whIntentFailedHandler = async ({
|
||||||
pIntentId,
|
pIntentId,
|
||||||
}: {
|
}: {
|
||||||
pIntentId: Payments["intent_id"];
|
pIntentId: Ordini["intent_id"];
|
||||||
}) => {
|
}) => {
|
||||||
const userId = await db
|
const userId = await db
|
||||||
.updateTable("payments")
|
.updateTable("ordini")
|
||||||
.set({
|
.set({
|
||||||
paymentstatus: PaymentStatusEnum.failed,
|
paymentstatus: PaymentStatusEnum.failed,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,7 @@ export const getPrezziario = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPrezziarioByIdHandler = async ({
|
export const getPrezziarioByIdHandler = async (id: PrezziarioIdprezziario) => {
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
id: PrezziarioIdprezziario;
|
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
return await db
|
return await db
|
||||||
.selectFrom("prezziario")
|
.selectFrom("prezziario")
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ export const genPdfSchedaAnnuncioBase64 = async (annuncioId: AnnunciId) => {
|
||||||
|
|
||||||
export const genPdfSchedaAnnuncio = async (annuncioId: AnnunciId) => {
|
export const genPdfSchedaAnnuncio = async (annuncioId: AnnunciId) => {
|
||||||
const keybd = getKeydbClient();
|
const keybd = getKeydbClient();
|
||||||
const url = `/area-riservata/admin/scheda-annuncio-stampa/${annuncioId}?raw=true`;
|
const url = `/area-riservata/scheda-annuncio-stampa/${annuncioId}?raw=true`;
|
||||||
const options: PDFOptions = { printBackground: true };
|
const options: PDFOptions = { printBackground: true };
|
||||||
if (keybd) {
|
if (keybd) {
|
||||||
const cacheKey = `${SCHEDA_ANNUNCIO_CACHE_PREFIX}${annuncioId}`;
|
const cacheKey = `${SCHEDA_ANNUNCIO_CACHE_PREFIX}${annuncioId}`;
|
||||||
|
|
@ -141,11 +141,15 @@ const genPdfCondizioni = async (condizioniId: TestiEStringheStingaId) => {
|
||||||
return await puppeteerGenPdf({ url });
|
return await puppeteerGenPdf({ url });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const genPdfRicevutaCortesia = async (ordineId: OrdiniOrdineId) => {
|
||||||
|
const url = `/servizio/ricevuta-cortesia/${ordineId}?raw=true`;
|
||||||
|
return await puppeteerGenPdf({ url });
|
||||||
|
};
|
||||||
|
|
||||||
export const genPdfRicevutaCortesiaBase64 = async (
|
export const genPdfRicevutaCortesiaBase64 = async (
|
||||||
ordineId: OrdiniOrdineId,
|
ordineId: OrdiniOrdineId,
|
||||||
) => {
|
) => {
|
||||||
const url = `/servizio/ricevuta-cortesia/${ordineId}?raw=true`;
|
const pdf = await genPdfRicevutaCortesia(ordineId);
|
||||||
const pdf = await puppeteerGenPdf({ url });
|
|
||||||
const pdfBase64 = Buffer.from(pdf).toString("base64");
|
const pdfBase64 = Buffer.from(pdf).toString("base64");
|
||||||
const base64Content = pdfBase64.includes("base64,")
|
const base64Content = pdfBase64.includes("base64,")
|
||||||
? pdfBase64.split("base64,")[1]
|
? pdfBase64.split("base64,")[1]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import type { EtichetteIdEtichetta } from "~/schemas/public/Etichette";
|
||||||
import type { FlagsId } from "~/schemas/public/Flags";
|
import type { FlagsId } from "~/schemas/public/Flags";
|
||||||
import type { MessagesMessageid } from "~/schemas/public/Messages";
|
import type { MessagesMessageid } from "~/schemas/public/Messages";
|
||||||
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import type { PaymentsId } from "~/schemas/public/Payments";
|
|
||||||
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
|
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
|
|
@ -77,11 +76,6 @@ export const zServizioId = z.custom<ServizioServizioId>(
|
||||||
"falied to validate ServizioId",
|
"falied to validate ServizioId",
|
||||||
);
|
);
|
||||||
|
|
||||||
export const zPagamentoId = z.custom<PaymentsId>(
|
|
||||||
(val) => typeof val === "string",
|
|
||||||
"falied to validate PagamentoId",
|
|
||||||
);
|
|
||||||
|
|
||||||
// export const zComuneId = z.custom<ComuniId>(
|
// export const zComuneId = z.custom<ComuniId>(
|
||||||
// (val) => typeof val === "number",
|
// (val) => typeof val === "number",
|
||||||
// "falied to validate ComuneId",
|
// "falied to validate ComuneId",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue