From d0f146b99e060f3134a769219782233962282984 Mon Sep 17 00:00:00 2001 From: Marco Pedone Date: Fri, 8 May 2026 14:16:29 +0200 Subject: [PATCH] isDisabled bool --- apps/db/migrations/nulla_fatto_flag.up.sql | 3 + apps/infoalloggi/Dockerfile | 2 - .../components/servizio/parametri_ricerca.tsx | 1 + .../src/components/servizio/servizio.tsx | 61 ++++++++++ .../components/servizio/servizio_actions.tsx | 39 +----- .../src/components/tables/richieste-table.tsx | 13 ++ apps/infoalloggi/src/env.ts | 1 - .../src/forms/FormEditServizioAdmin.tsx | 113 +++++++++++++++--- apps/infoalloggi/src/i18n/en.ts | 5 +- apps/infoalloggi/src/i18n/it.ts | 5 +- apps/infoalloggi/src/i18n/locales.ts | 3 + .../src/schemas/public/Servizio.ts | 5 + .../src/server/api/routers/servizio.ts | 1 + .../server/controllers/servizio.controller.ts | 2 + .../src/server/services/servizio.service.ts | 15 ++- 15 files changed, 212 insertions(+), 57 deletions(-) create mode 100644 apps/db/migrations/nulla_fatto_flag.up.sql diff --git a/apps/db/migrations/nulla_fatto_flag.up.sql b/apps/db/migrations/nulla_fatto_flag.up.sql new file mode 100644 index 0000000..9325b17 --- /dev/null +++ b/apps/db/migrations/nulla_fatto_flag.up.sql @@ -0,0 +1,3 @@ +-- nulla_fatto_flag.up.sql +ALTER TABLE IF EXISTS public.servizio + ADD COLUMN IF NOT EXISTS "isDisabled" boolean NOT NULL DEFAULT false; \ No newline at end of file diff --git a/apps/infoalloggi/Dockerfile b/apps/infoalloggi/Dockerfile index fd4d48a..349826b 100644 --- a/apps/infoalloggi/Dockerfile +++ b/apps/infoalloggi/Dockerfile @@ -70,8 +70,6 @@ ARG EXP_API_USER ENV EXP_API_USER=$EXP_API_USER ARG EXP_API_PASS ENV EXP_API_PASS=$EXP_API_PASS -ARG TILES_URL -ENV TILES_URL=$TILES_URL ARG SKEBBY_USER ENV SKEBBY_USER=$SKEBBY_USER ARG SKEBBY_PASS diff --git a/apps/infoalloggi/src/components/servizio/parametri_ricerca.tsx b/apps/infoalloggi/src/components/servizio/parametri_ricerca.tsx index bb36a80..65a19d4 100644 --- a/apps/infoalloggi/src/components/servizio/parametri_ricerca.tsx +++ b/apps/infoalloggi/src/components/servizio/parametri_ricerca.tsx @@ -798,6 +798,7 @@ export const EditServizioModal = () => { disabled={ !isAdmin && (servizio.isInterrotto || + servizio.isDisabled || servizio.annunci.some((a) => a.status_conferma !== null)) } variant="secondary" diff --git a/apps/infoalloggi/src/components/servizio/servizio.tsx b/apps/infoalloggi/src/components/servizio/servizio.tsx index 2ed3126..0ee7060 100644 --- a/apps/infoalloggi/src/components/servizio/servizio.tsx +++ b/apps/infoalloggi/src/components/servizio/servizio.tsx @@ -11,6 +11,7 @@ import { ExternalLink, type LucideIcon, PackageCheck, + SearchSlash, SearchX, Timer, Trash2, @@ -161,6 +162,9 @@ const ServizioLinkCard = ({ if (servizio.isInterrotto) { status = "interrotto"; StatusIcon = SearchX; + } else if (servizio.isDisabled) { + status = "disabled"; + StatusIcon = SearchSlash; } else if (!servizio.isOkAcconto) { status = "in_attesa"; StatusIcon = ClockFading; @@ -223,6 +227,7 @@ const ServizioLinkCard = ({ className={cn( status === "attivo" && "border-green-500", status === "interrotto" && "border-red-500", + status === "disabled" && "border-gray-500", status === "scaduto" && "border-gray-500", status === "completato" && "border-blue-500", "transition-shadow", @@ -243,6 +248,7 @@ const ServizioLinkCard = ({ "flex items-center gap-2", status === "attivo" && "text-green-500", status === "interrotto" && "text-red-500", + status === "disabled" && "text-gray-500", status === "scaduto" && "text-gray-500", status === "completato" && "text-blue-500", )} @@ -436,6 +442,61 @@ const Main = () => { ); } + if (servizio.isDisabled) { + if (isAdmin) { + return ( + <> +
+
+ + Servizio Disattivato +
+
+
+ {servizio.annunci.map((data) => { + return ( + + + + ); + })} + {servizio.annunci.length > 1 && + servizio.annunci.length % 2 === 1 && ( +
+ )} +
+ + ); + } + return ( +
+
+ +
+
+

+ {t.servizio.servizio_disattivato} +

+

+ {t.servizio.servizio_disattivato_desc} +

+
+ + + +
+ ); + } // mostra onboarding if (!servizio.onboardOk) { return ( diff --git a/apps/infoalloggi/src/components/servizio/servizio_actions.tsx b/apps/infoalloggi/src/components/servizio/servizio_actions.tsx index fa7a246..05fc836 100644 --- a/apps/infoalloggi/src/components/servizio/servizio_actions.tsx +++ b/apps/infoalloggi/src/components/servizio/servizio_actions.tsx @@ -1,5 +1,4 @@ import { - Bug, Check, Cog, Euro, @@ -7,7 +6,6 @@ import { Logs, Plus, Search, - Trash2, } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -27,11 +25,6 @@ import { LoadingPage } from "~/components/loading"; import { InterruzioneServizio } from "~/components/servizio/interruzione"; import { EditServizioModal } from "~/components/servizio/parametri_ricerca"; import { Button } from "~/components/ui/button"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "~/components/ui/collapsible"; import { Dialog, DialogContent, @@ -568,33 +561,11 @@ const EditServizioAdmin = () => { - - - - - - - - {(() => { - const { annunci, ...rest } = servizio; - return
{JSON.stringify(rest, null, 2)}
; - })()} -
-
- { - remove({ servizioId }); - }} - title="Elimina servizio" - > - - + remove({ servizioId })} + initialData={servizio} + onSubmit={onSubmit} + />
diff --git a/apps/infoalloggi/src/components/tables/richieste-table.tsx b/apps/infoalloggi/src/components/tables/richieste-table.tsx index 816dba9..35c6341 100644 --- a/apps/infoalloggi/src/components/tables/richieste-table.tsx +++ b/apps/infoalloggi/src/components/tables/richieste-table.tsx @@ -17,6 +17,7 @@ import { ExternalLink, Logs, ReceiptEuro, + SearchSlash, SearchX, UserCog, } from "lucide-react"; @@ -75,6 +76,7 @@ type RichiesteTableProps = { const StatusMapping = { attesa_attivazione: "Attesa di attivazione", interrotto: "Interrotto", + disabled: "Disattivato", onboarding: "Onboarding", scaduto: "Scaduto", consulenza_da_pagare: "Consulenza da pagare", @@ -93,6 +95,9 @@ function statusParser(data: RichiesteData): keyof typeof StatusMapping { if (data.isInterrotto) { return "interrotto"; } + if (data.isDisabled) { + return "disabled"; + } if (!data.onboardOk) { return "onboarding"; } @@ -380,6 +385,9 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => { } else if (row.original.isInterrotto) { decorrenzaColor = "text-destructive"; decorrenzaTooltip = "Servizio interrotto"; + } else if (row.original.isDisabled) { + decorrenzaColor = "text-muted-foreground"; + decorrenzaTooltip = "Servizio disattivato"; } else if (row.original.isOkAcconto && !row.original.isOkSaldo) { decorrenzaColor = "text-warning"; decorrenzaTooltip = "Attesa di saldo"; @@ -452,6 +460,10 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => { "border-destructive bg-destructive/10 text-destructive"; icon = ; break; + case "disabled": + badgeClass = "border-gray-500 bg-muted/10 text-muted-foreground"; + icon = ; + break; case "onboarding": badgeClass = "border-pink-500 bg-pink-500/10 text-pink-600"; icon = ( @@ -543,6 +555,7 @@ export const RichiesteTable = ({ data }: RichiesteTableProps) => { accessorKey: "interruzione_expires_at", cell: ({ row }) => { if (row.original.isInterrotto) return "---"; + if (row.original.isDisabled) return "---"; if (!row.original.decorrenza) return "---"; const expiresAt = add(row.original.decorrenza, { days: row.original.interruzioneDays, diff --git a/apps/infoalloggi/src/env.ts b/apps/infoalloggi/src/env.ts index cda98e3..a9ceba3 100644 --- a/apps/infoalloggi/src/env.ts +++ b/apps/infoalloggi/src/env.ts @@ -22,7 +22,6 @@ export const env = createEnv({ POSTGRES_USER: z.string(), STRIPE_SECRET_KEY: z.string(), STRIPE_WEBHOOK_SECRET: z.string(), - TILES_URL: z.string(), SKEBBY_USER: z.string(), SKEBBY_PASS: z.string(), REVALIDATION_SECRET: z.string(), diff --git a/apps/infoalloggi/src/forms/FormEditServizioAdmin.tsx b/apps/infoalloggi/src/forms/FormEditServizioAdmin.tsx index 5a18c24..34df9f9 100644 --- a/apps/infoalloggi/src/forms/FormEditServizioAdmin.tsx +++ b/apps/infoalloggi/src/forms/FormEditServizioAdmin.tsx @@ -1,5 +1,6 @@ -import { X } from "lucide-react"; +import { Bug, Trash2, X } from "lucide-react"; import { z } from "zod/v4"; +import { Confirm } from "~/components/confirm"; import { Counter } from "~/components/counter"; import { Form, @@ -12,26 +13,33 @@ import { } from "~/components/custom_ui/form"; import { DateInput } from "~/components/custom_ui/inputDate"; import { Button } from "~/components/ui/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "~/components/ui/collapsible"; import { Label } from "~/components/ui/label"; import { Switch } from "~/components/ui/switch"; import { cn } from "~/lib/utils"; import { useZodForm } from "~/lib/zodForm"; import { useTranslation } from "~/providers/I18nProvider"; -import type { Servizio } from "~/schemas/public/Servizio"; +import { ServizioSchema } from "~/schemas/public/Servizio"; +import type { ServizioData } from "~/server/controllers/servizio.controller"; import { DEFAULT_SERVIZIO_INTERRUZIONE_DURATION_DAYS } from "~/utils/config"; -const Schema = z.object({ - decorrenza: z.date().nullable(), - isInterrotto: z.boolean(), - isOkAcconto: z.boolean(), - isOkConsulenza: z.boolean(), - isOkSaldo: z.boolean(), - skipPayment: z.boolean(), - skipControlloDoc: z.boolean(), - skipDocMotivazione: z.boolean(), - onboardOk: z.boolean(), - interruzioneDays: z.number(), - acceptedInterr: z.boolean(), +const Schema = ServizioSchema.pick({ + decorrenza: true, + isInterrotto: true, + isOkAcconto: true, + isOkConsulenza: true, + isOkSaldo: true, + skipPayment: true, + skipControlloDoc: true, + skipDocMotivazione: true, + onboardOk: true, + interruzioneDays: true, + acceptedInterr: true, + isDisabled: true, }); export type FormValues = z.infer; @@ -39,16 +47,29 @@ export type FormValues = z.infer; export const FormEditServizio = ({ initialData, onSubmit, + handleRemove, }: { - initialData: Servizio | undefined; + initialData: ServizioData | undefined; onSubmit: (fields: FormValues) => void; + handleRemove: () => void; }) => { // This can come from your database or API. let defaultValues: FormValues; if (initialData) { defaultValues = { - ...initialData, + decorrenza: initialData.decorrenza, + isInterrotto: initialData.isInterrotto, + isOkAcconto: initialData.isOkAcconto, + isOkConsulenza: initialData.isOkConsulenza, + isOkSaldo: initialData.isOkSaldo, + skipPayment: initialData.skipPayment, + skipControlloDoc: initialData.skipControlloDoc, + skipDocMotivazione: initialData.skipDocMotivazione, + onboardOk: initialData.onboardOk, + interruzioneDays: initialData.interruzioneDays, + acceptedInterr: initialData.acceptedInterr, + isDisabled: initialData.isDisabled, }; } else { defaultValues = { @@ -63,6 +84,7 @@ export const FormEditServizio = ({ onboardOk: false, interruzioneDays: DEFAULT_SERVIZIO_INTERRUZIONE_DURATION_DAYS, acceptedInterr: false, + isDisabled: false, }; } const { locale, t } = useTranslation(); @@ -147,6 +169,7 @@ export const FormEditServizio = ({ )} />
+
+
- +
+ ( + +
+ DISABILITATO + + + + +
+ + + Disabilita il servizio, chiude la posizione senza eliminare. + +
+ )} + /> + +
+ + + + + + + { + handleRemove(); + }} + title="Elimina servizio" + > + + +
+ + {(() => { + if (!initialData) return null; + const { annunci, ...rest } = initialData; + return
{JSON.stringify(rest, null, 2)}
; + })()} +
+
); diff --git a/apps/infoalloggi/src/i18n/en.ts b/apps/infoalloggi/src/i18n/en.ts index 31753d1..d8fb8d7 100644 --- a/apps/infoalloggi/src/i18n/en.ts +++ b/apps/infoalloggi/src/i18n/en.ts @@ -1320,6 +1320,7 @@ The Stable Rent service, ideal for those with demonstrable work references and f interrotto: "Interrupted", scaduto: "Expired", completato: "Completed", + disabled: "Disabled", }, torna_alla_dashboard: "Dashboard", dettaglio_servizio: "Service Details", @@ -1330,7 +1331,9 @@ The Stable Rent service, ideal for those with demonstrable work references and f durata_servizio: "Service Duration:", servizio_interrotto: "Service Interrupted", servizio_interrotto_desc: - "This service cannot be reused as it has been interrupted.", + "This service cannot be used as it has been interrupted.", + servizio_disattivato: "Service Disabled", + servizio_disattivato_desc: "This service cannot be used.", non_attivo: "Service not yet activated", attiva_cta: "Activate the Service Now!", per_te_1: "Already selected", diff --git a/apps/infoalloggi/src/i18n/it.ts b/apps/infoalloggi/src/i18n/it.ts index b0d9e01..45a9749 100644 --- a/apps/infoalloggi/src/i18n/it.ts +++ b/apps/infoalloggi/src/i18n/it.ts @@ -1319,6 +1319,7 @@ export const it: LangDict = { in_attesa: "In Attesa di Attivazione", scaduto: "Scaduto", completato: "Completato", + disabled: "Disattivato", }, torna_alla_dashboard: "Dashboard", dettaglio_servizio: "Dettaglio Servizio", @@ -1329,7 +1330,9 @@ export const it: LangDict = { durata_servizio: "Durata servizio:", servizio_interrotto: "Servizio interrotto", servizio_interrotto_desc: - "Non è possibile riutilizzare questo servizio, poichè è stato interrotto.", + "Non è possibile utilizzare questo servizio, poichè è stato interrotto.", + servizio_disattivato: "Servizio disattivato", + servizio_disattivato_desc: "Non è possibile utilizzare questo servizio.", non_attivo: "Servizio non ancora attivato", attiva_cta: "Attiva il Servizio Ora!", per_te_1: "Sono già stati selezionati", diff --git a/apps/infoalloggi/src/i18n/locales.ts b/apps/infoalloggi/src/i18n/locales.ts index 584c308..d618fd4 100644 --- a/apps/infoalloggi/src/i18n/locales.ts +++ b/apps/infoalloggi/src/i18n/locales.ts @@ -779,6 +779,7 @@ export type LangDict = { in_attesa: string; scaduto: string; completato: string; + disabled: string; }; torna_alla_dashboard: string; dettaglio_servizio: string; @@ -789,6 +790,8 @@ export type LangDict = { durata_servizio: string; servizio_interrotto: string; servizio_interrotto_desc: string; + servizio_disattivato: string; + servizio_disattivato_desc: string; non_attivo: string; attiva_cta: string; per_te_1: string; diff --git a/apps/infoalloggi/src/schemas/public/Servizio.ts b/apps/infoalloggi/src/schemas/public/Servizio.ts index c5575cf..a595901 100644 --- a/apps/infoalloggi/src/schemas/public/Servizio.ts +++ b/apps/infoalloggi/src/schemas/public/Servizio.ts @@ -72,6 +72,8 @@ export default interface ServizioTable { interruzioneDays: ColumnType; acceptedInterr: ColumnType; + + isDisabled: ColumnType; } export type Servizio = Selectable; @@ -115,6 +117,7 @@ export const ServizioSchema = z.object({ onboardOk: z.boolean(), interruzioneDays: z.number(), acceptedInterr: z.boolean(), + isDisabled: z.boolean(), }); export const NewServizioSchema = z.object({ @@ -150,6 +153,7 @@ export const NewServizioSchema = z.object({ onboardOk: z.boolean().optional(), interruzioneDays: z.number().optional(), acceptedInterr: z.boolean().optional(), + isDisabled: z.boolean().optional(), }); export const ServizioUpdateSchema = z.object({ @@ -185,4 +189,5 @@ export const ServizioUpdateSchema = z.object({ onboardOk: z.boolean().optional(), interruzioneDays: z.number().optional(), acceptedInterr: z.boolean().optional(), + isDisabled: z.boolean().optional(), }); \ No newline at end of file diff --git a/apps/infoalloggi/src/server/api/routers/servizio.ts b/apps/infoalloggi/src/server/api/routers/servizio.ts index 5c80b21..d9f8e91 100644 --- a/apps/infoalloggi/src/server/api/routers/servizio.ts +++ b/apps/infoalloggi/src/server/api/routers/servizio.ts @@ -198,6 +198,7 @@ export const servizioRouter = createTRPCRouter({ !servizio || servizio.isOkAcconto || servizio.isInterrotto || + servizio.isDisabled || servizio.decorrenza !== null ) { return { available: false, userId: null }; diff --git a/apps/infoalloggi/src/server/controllers/servizio.controller.ts b/apps/infoalloggi/src/server/controllers/servizio.controller.ts index f1d3aba..f8ae8a4 100644 --- a/apps/infoalloggi/src/server/controllers/servizio.controller.ts +++ b/apps/infoalloggi/src/server/controllers/servizio.controller.ts @@ -1143,6 +1143,7 @@ export type RichiesteData = Pick< | "servizio_id" | "user_id" | "isInterrotto" + | "isDisabled" | "isOkAcconto" | "isOkSaldo" | "decorrenza" @@ -1184,6 +1185,7 @@ export const getRichieste = async (): Promise => { "servizio.created_at", "servizio.user_id", "servizio.isInterrotto", + "servizio.isDisabled", "servizio.isOkAcconto", "servizio.isOkSaldo", "servizio.decorrenza", diff --git a/apps/infoalloggi/src/server/services/servizio.service.ts b/apps/infoalloggi/src/server/services/servizio.service.ts index 267fdb5..c4ce998 100644 --- a/apps/infoalloggi/src/server/services/servizio.service.ts +++ b/apps/infoalloggi/src/server/services/servizio.service.ts @@ -7,6 +7,7 @@ import type { ServizioUpdate, } from "~/schemas/public/Servizio"; import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci"; +import { clearUserEmailQueue } from "~/server/controllers/event_queue.controller"; import { db } from "../db"; export const addServizio = async (data: NewServizio) => { @@ -78,7 +79,14 @@ export const updateServizio = async ({ data: ServizioUpdate; }) => { try { - return await db + const isDisabledPrev = await db + .selectFrom("servizio") + .select("isDisabled") + .where("servizio_id", "=", servizioId) + .executeTakeFirstOrThrow( + () => new Error(`Servizio not found - servizioId: ${servizioId}`), + ); + const updated = await db .updateTable("servizio") .set(data) .where("servizio_id", "=", servizioId) @@ -87,6 +95,11 @@ export const updateServizio = async ({ () => new Error(`Failed to update servizio - servizioId: ${servizioId}`), ); + if (!isDisabledPrev.isDisabled && updated.isDisabled) { + // Only the on isDisabled false->true change + await clearUserEmailQueue(updated.user_id); + } + return updated; } catch (e) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR",