refactor: remove ServiziHeader component and integrate its functionality directly into RicercaUser page
feat: update Servizio component to handle interruzioneDays and acceptedInterr logic refactor: simplify servizio_actions by removing unused InterruzioneServizio and EditParametri components feat: enhance FormEditServizioAdmin to include interruzioneDays and acceptedInterr fields fix: adjust email service to delete obsolete emails and handle invalid email types gracefully chore: update stripe controller to utilize interruzioneDays for lock expiration logic fix: ensure correct handling of servizio expiration and interruzione logic in service controller
This commit is contained in:
parent
b4bfccd4c7
commit
5bff7219e8
15 changed files with 579 additions and 368 deletions
7
apps/db/migrations/40_servizio_interr.up.sql
Normal file
7
apps/db/migrations/40_servizio_interr.up.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE IF EXISTS public.servizio
|
||||||
|
ADD COLUMN IF NOT EXISTS "interruzioneDays" integer NOT NULL DEFAULT 14;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.servizio
|
||||||
|
ADD COLUMN IF NOT EXISTS "acceptedInterr" boolean NOT NULL DEFAULT FALSE;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.servizio DROP COLUMN IF EXISTS "isSent";
|
||||||
|
|
@ -120,6 +120,7 @@ const SelectedComp = memo(
|
||||||
href={`/annuncio/${selected.codice}`}
|
href={`/annuncio/${selected.codice}`}
|
||||||
initial={{ opacity: 0, scale: 0 }}
|
initial={{ opacity: 0, scale: 0 }}
|
||||||
key={selected.id}
|
key={selected.id}
|
||||||
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
transition={{
|
transition={{
|
||||||
duration: 0.1,
|
duration: 0.1,
|
||||||
|
|
|
||||||
249
apps/infoalloggi/src/components/servizio/interruzione.tsx
Normal file
249
apps/infoalloggi/src/components/servizio/interruzione.tsx
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
import { add, differenceInDays, isBefore } from "date-fns";
|
||||||
|
import { ArrowRight, Clock, ClockAlert, XCircle } from "lucide-react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { Confirm } from "~/components/confirm";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "~/components/ui/alert-dialog";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "~/components/ui/card";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "~/components/ui/tooltip";
|
||||||
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
import { useServizio } from "~/providers/ServizioProvider";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
|
export const DisclaimerInterruzioneServizio = () => {
|
||||||
|
const { servizio, servizioId } = useServizio();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const { mutate: interrompiServizio } =
|
||||||
|
api.servizio.interruzioneServizio.useMutation({
|
||||||
|
onError: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Errore durante la richiesta di interruzione");
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
toast.success("Interruzione richiesta");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
|
||||||
|
onError: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Errore");
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!servizio.decorrenza) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="mx-auto max-w-4xl px-4 py-6 md:py-8">
|
||||||
|
<div className="mb-10 text-center">
|
||||||
|
<div className="mx-auto mb-6 flex size-16 items-center justify-center rounded-full bg-secondary">
|
||||||
|
<Clock className="size-8 text-primary" />
|
||||||
|
</div>
|
||||||
|
<h1 className="mb-4 text-balance font-bold text-2xl text-foreground tracking-tight md:text-4xl">
|
||||||
|
Sono passati {differenceInDays(new Date(), servizio.decorrenza)}{" "}
|
||||||
|
giorni dall'inizio della tua ricerca
|
||||||
|
</h1>
|
||||||
|
<p className="mx-auto max-w-xl text-pretty text-lg text-muted-foreground leading-relaxed">
|
||||||
|
Hai trovato qualche immobile interessante? <br /> Ricordati che{" "}
|
||||||
|
<b>se non intendi confermare</b> nessun immobile, puoi interrompere la
|
||||||
|
ricerca <b>entro 14 giorni dalla decorrenza</b>, evitando così di
|
||||||
|
dover pagare il saldo del servizio acquistato.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
<Card className="border-2 border-primary shadow-lg">
|
||||||
|
<CardHeader className="pb-2 text-center">
|
||||||
|
<div className="mx-auto mb-3 flex size-14 items-center justify-center rounded-full bg-primary/10">
|
||||||
|
<ArrowRight className="size-7 text-primary" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-foreground text-xl">Continua</CardTitle>
|
||||||
|
<CardDescription className="text-base">
|
||||||
|
Continua a cercare casa
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
|
<CardContent className="">
|
||||||
|
<p className="mb-4 text-muted-foreground text-sm">
|
||||||
|
Avrai sempre modo di interrompere il servizio usando il pulsante
|
||||||
|
relativo.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
className="h-12 w-full bg-primary font-medium text-base text-primary-foreground hover:bg-primary/90"
|
||||||
|
onClick={() =>
|
||||||
|
updateServizio({ servizioId, data: { acceptedInterr: true } })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Continua con la ricerca
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card className="border-border">
|
||||||
|
<CardHeader className="pb-2 text-center">
|
||||||
|
<div className="mx-auto mb-3 flex size-14 items-center justify-center rounded-full bg-muted">
|
||||||
|
<XCircle className="size-7 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-foreground text-xl">
|
||||||
|
Interrompi
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="text-base">
|
||||||
|
Termina la tua ricerca ora
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
|
<CardContent className="">
|
||||||
|
<ul className="mb-6 space-y-2 text-muted-foreground text-sm">
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<span className="size-1.5 rounded-full bg-muted-foreground" />
|
||||||
|
Terminazione immediata del servizio
|
||||||
|
</li>
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<span className="size-1.5 rounded-full bg-muted-foreground" />
|
||||||
|
Nessun addebito aggiuntivo applicato
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Confirm
|
||||||
|
description={t.servizio.interruzione.description}
|
||||||
|
onConfirm={() => {
|
||||||
|
interrompiServizio({ servizioId });
|
||||||
|
}}
|
||||||
|
title={t.servizio.interruzione.title}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="h-12 w-full border-border font-medium text-base text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Interrompi il mio servizio ora
|
||||||
|
</Button>
|
||||||
|
</Confirm>
|
||||||
|
</CardFooter>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<p className="mt-8 text-center text-muted-foreground text-sm">
|
||||||
|
Hai fino al{" "}
|
||||||
|
{add(servizio.decorrenza, {
|
||||||
|
days: servizio.interruzioneDays,
|
||||||
|
}).toLocaleDateString("it-IT")}{" "}
|
||||||
|
per interrompere il servizio senza costi aggiuntivi.
|
||||||
|
<br />
|
||||||
|
Se hai bisogno di assistenza, contattaci.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InterruzioneServizio = () => {
|
||||||
|
const { servizio, isAdmin } = useServizio();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const { mutate } = api.servizio.interruzioneServizio.useMutation({
|
||||||
|
onError: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Errore durante la richiesta di interruzione");
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
toast.success("Interruzione richiesta");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!servizio.decorrenza) return null;
|
||||||
|
const fineInterruzione = add(servizio.decorrenza, {
|
||||||
|
days: servizio.interruzioneDays,
|
||||||
|
});
|
||||||
|
|
||||||
|
const canInterrompere =
|
||||||
|
!servizio.isInterrotto &&
|
||||||
|
(isAdmin ||
|
||||||
|
(isBefore(new Date(), fineInterruzione) &&
|
||||||
|
servizio.annunci.every((a) => a.accettato_conferma_at === null)));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
{canInterrompere ? (
|
||||||
|
<Button
|
||||||
|
aria-label="Request Interruzione"
|
||||||
|
className="w-full"
|
||||||
|
disabled={!canInterrompere}
|
||||||
|
>
|
||||||
|
<ClockAlert /> <span>{t.servizio.interruzione.btn}</span>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Tooltip delayDuration={0}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="inline-block w-full">
|
||||||
|
<Button
|
||||||
|
aria-label="Request Interruzione"
|
||||||
|
className="w-full"
|
||||||
|
disabled={!canInterrompere}
|
||||||
|
>
|
||||||
|
<ClockAlert /> <span>{t.servizio.interruzione.btn}</span>
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Periodo di interruzione scaduto, contattaci.</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{t.servizio.interruzione.title}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="space-y-2">
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Fine periodo di interruzione:{" "}
|
||||||
|
{fineInterruzione.toLocaleDateString("it-IT")}
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
<p className="whitespace-pre-line">
|
||||||
|
{t.servizio.interruzione.description}
|
||||||
|
</p>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>{t.annulla}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
aria-label="Confirm Interruzione"
|
||||||
|
onClick={() => mutate({ servizioId: servizio.servizio_id })}
|
||||||
|
>
|
||||||
|
{t.conferma}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { it } from "date-fns/locale";
|
import { it } from "date-fns/locale";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, SlidersHorizontal } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { Counter } from "~/components/counter";
|
import { Counter } from "~/components/counter";
|
||||||
|
import {
|
||||||
|
Credenza,
|
||||||
|
CredenzaBody,
|
||||||
|
CredenzaContent,
|
||||||
|
CredenzaDescription,
|
||||||
|
CredenzaHeader,
|
||||||
|
CredenzaTitle,
|
||||||
|
CredenzaTrigger,
|
||||||
|
} from "~/components/custom_ui/credenza";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -16,6 +27,14 @@ import { MultiSelect, UnpackOptions } from "~/components/custom_ui/multiselect";
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Calendar } from "~/components/ui/calendar";
|
import { Calendar } from "~/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "~/components/ui/dialog";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
|
|
@ -24,12 +43,16 @@ import {
|
||||||
} from "~/components/ui/popover";
|
} from "~/components/ui/popover";
|
||||||
import { Slider } from "~/components/ui/slider";
|
import { Slider } from "~/components/ui/slider";
|
||||||
import { Switch } from "~/components/ui/switch";
|
import { Switch } from "~/components/ui/switch";
|
||||||
|
import { FieldResetButton } from "~/forms/comps";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useZodForm } from "~/lib/zodForm";
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
import { useServizio } from "~/providers/ServizioProvider";
|
||||||
import type { Servizio } from "~/schemas/public/Servizio";
|
import type { Servizio } from "~/schemas/public/Servizio";
|
||||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import { FieldResetButton } from "./comps";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
import { DEFAULT_SERVIZIO_INTERRUZIONE_DURATION_DAYS } from "~/utils/config";
|
||||||
|
|
||||||
const Schema = z
|
const Schema = z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -53,6 +76,8 @@ const Schema = z
|
||||||
skipPayment: z.boolean(),
|
skipPayment: z.boolean(),
|
||||||
skipControlloDoc: z.boolean(),
|
skipControlloDoc: z.boolean(),
|
||||||
skipDocMotivazione: z.boolean(),
|
skipDocMotivazione: z.boolean(),
|
||||||
|
interruzioneDays: z.number().min(0),
|
||||||
|
acceptedInterr: z.boolean(),
|
||||||
})
|
})
|
||||||
.superRefine(({ n_adulti }, refinementContext) => {
|
.superRefine(({ n_adulti }, refinementContext) => {
|
||||||
if (n_adulti <= 0) {
|
if (n_adulti <= 0) {
|
||||||
|
|
@ -65,9 +90,9 @@ const Schema = z
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormValues = z.infer<typeof Schema>;
|
type FormValues = z.infer<typeof Schema>;
|
||||||
|
|
||||||
export const FormServizio = ({
|
const FormServizio = ({
|
||||||
initialData,
|
initialData,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
isLimited,
|
isLimited,
|
||||||
|
|
@ -105,6 +130,8 @@ export const FormServizio = ({
|
||||||
skipPayment: false,
|
skipPayment: false,
|
||||||
skipControlloDoc: true,
|
skipControlloDoc: true,
|
||||||
skipDocMotivazione: true,
|
skipDocMotivazione: true,
|
||||||
|
interruzioneDays: DEFAULT_SERVIZIO_INTERRUZIONE_DURATION_DAYS, // default value for interruzioneDays
|
||||||
|
acceptedInterr: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { locale, t } = useTranslation();
|
const { locale, t } = useTranslation();
|
||||||
|
|
@ -613,6 +640,7 @@ export const FormServizio = ({
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
<Separator />
|
<Separator />
|
||||||
{!isLimited && (
|
{!isLimited && (
|
||||||
|
|
@ -701,3 +729,114 @@ export const FormServizio = ({
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export const NewServizioModal = ({ userId }: { userId: UsersId }) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const { mutate: add } = api.servizio.addServizio.useMutation({
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
toast.success("Servizio creato con successo");
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
|
||||||
|
setOpen(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSubmit(fields: FormValues) {
|
||||||
|
add({ data: { ...fields, user_id: userId } });
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dialog onOpenChange={setOpen} open={open}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Nuovo Servizio
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-h-[80%] w-full overflow-auto p-4 sm:max-w-5xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Creazione Servizio</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">desc</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="overflow-y-auto">
|
||||||
|
<FormServizio
|
||||||
|
initialData={undefined}
|
||||||
|
isLimited={false}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditServizioModal = () => {
|
||||||
|
const { servizioId, isAdmin, servizio } = useServizio();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [openEditParametri, setOpenEditParametri] = useState(false);
|
||||||
|
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
toast.success("Servizio modificato con successo");
|
||||||
|
await utils.servizio.invalidate();
|
||||||
|
|
||||||
|
setOpenEditParametri(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSubmit(fields: FormValues) {
|
||||||
|
update({
|
||||||
|
data: {
|
||||||
|
...fields,
|
||||||
|
},
|
||||||
|
servizioId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Credenza onOpenChange={setOpenEditParametri} open={openEditParametri}>
|
||||||
|
<CredenzaTrigger asChild>
|
||||||
|
<Button
|
||||||
|
aria-label="Parametri di ricerca"
|
||||||
|
className="w-full"
|
||||||
|
disabled={
|
||||||
|
!isAdmin &&
|
||||||
|
(servizio.isInterrotto ||
|
||||||
|
servizio.annunci.some((a) => a.hasConfermaAdmin))
|
||||||
|
}
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
<SlidersHorizontal />
|
||||||
|
{t.servizio.parametri.btn}
|
||||||
|
</Button>
|
||||||
|
</CredenzaTrigger>
|
||||||
|
<CredenzaContent className="max-h-[90vh] w-full sm:max-w-5xl">
|
||||||
|
<CredenzaHeader>
|
||||||
|
<CredenzaTitle>{t.servizio.parametri.title}</CredenzaTitle>
|
||||||
|
<CredenzaDescription className="sr-only">
|
||||||
|
{t.servizio.parametri.title}
|
||||||
|
</CredenzaDescription>
|
||||||
|
</CredenzaHeader>
|
||||||
|
<CredenzaBody className="max-h-[80vh] w-full max-w-3xl overflow-y-auto pb-5 sm:max-w-5xl">
|
||||||
|
<FormServizio
|
||||||
|
initialData={servizio}
|
||||||
|
isLimited={!isAdmin}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
/>
|
||||||
|
</CredenzaBody>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { NewRinnovoModal } from "~/components/servizio/rinnovo";
|
|
||||||
import { FormServizio, type FormValues } from "~/forms/FormServizio";
|
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
|
||||||
import { api } from "~/utils/api";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "../ui/dialog";
|
|
||||||
|
|
||||||
export const ServiziHeader = ({ userId }: { userId: UsersId }) => {
|
|
||||||
return (
|
|
||||||
<div className="flex w-full flex-wrap items-center justify-between gap-2">
|
|
||||||
<h3 className="font-semibold text-xl">Servizi cliente</h3>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<NewServizioModal userId={userId} />
|
|
||||||
<NewRinnovoModal userId={userId} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const NewServizioModal = ({ userId }: { userId: UsersId }) => {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const utils = api.useUtils();
|
|
||||||
|
|
||||||
const { mutate: add } = api.servizio.addServizio.useMutation({
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(error.message);
|
|
||||||
},
|
|
||||||
onSuccess: async () => {
|
|
||||||
toast.success("Servizio creato con successo");
|
|
||||||
await utils.servizio.invalidate();
|
|
||||||
|
|
||||||
setOpen(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function onSubmit(fields: FormValues) {
|
|
||||||
add({ data: { ...fields, user_id: userId } });
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Dialog onOpenChange={setOpen} open={open}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(true);
|
|
||||||
}}
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
Nuovo Servizio
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-h-[80%] w-full overflow-auto p-4 sm:max-w-5xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Creazione Servizio</DialogTitle>
|
|
||||||
<DialogDescription className="sr-only">desc</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="overflow-y-auto">
|
|
||||||
<FormServizio
|
|
||||||
initialData={undefined}
|
|
||||||
isLimited={false}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { add } from "date-fns";
|
import { add, isAfter, isBefore } from "date-fns";
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
BadgeCheck,
|
BadgeCheck,
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { DisclaimerInterruzioneServizio } from "~/components/servizio/interruzione";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import {
|
import {
|
||||||
|
|
@ -28,6 +29,7 @@ import type { Rinnovi } from "~/schemas/public/Rinnovi";
|
||||||
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 { DEFAULT_SERVIZIO_DURATION_DAYS } from "~/utils/config";
|
||||||
import { Confirm } from "../confirm";
|
import { Confirm } from "../confirm";
|
||||||
import { AlarmClockSVG } from "../svgs";
|
import { AlarmClockSVG } from "../svgs";
|
||||||
import {
|
import {
|
||||||
|
|
@ -165,7 +167,10 @@ const ServizioLinkCard = ({
|
||||||
} else if (servizio.decorrenza) {
|
} else if (servizio.decorrenza) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const decorrenza = new Date(servizio.decorrenza);
|
const decorrenza = new Date(servizio.decorrenza);
|
||||||
if (now > add(decorrenza, { days: 60 }) && !servizio.isOkSaldo) {
|
if (
|
||||||
|
now > add(decorrenza, { days: DEFAULT_SERVIZIO_DURATION_DAYS }) &&
|
||||||
|
!servizio.isOkSaldo
|
||||||
|
) {
|
||||||
status = "scaduto";
|
status = "scaduto";
|
||||||
StatusIcon = CircleMinus;
|
StatusIcon = CircleMinus;
|
||||||
}
|
}
|
||||||
|
|
@ -285,9 +290,9 @@ const Content = () => {
|
||||||
? new Date(servizio.decorrenza).toLocaleDateString("it-IT")
|
? new Date(servizio.decorrenza).toLocaleDateString("it-IT")
|
||||||
: "";
|
: "";
|
||||||
const end_txt = servizio.decorrenza
|
const end_txt = servizio.decorrenza
|
||||||
? new Date(add(servizio.decorrenza, { days: 60 })).toLocaleDateString(
|
? new Date(
|
||||||
"it-IT",
|
add(servizio.decorrenza, { days: DEFAULT_SERVIZIO_DURATION_DAYS }),
|
||||||
)
|
).toLocaleDateString("it-IT")
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -341,6 +346,7 @@ const Main = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// se interrotto, mostra avvisos
|
||||||
if (servizio.isInterrotto) {
|
if (servizio.isInterrotto) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -349,6 +355,7 @@ const Main = () => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// mostra onboarding
|
||||||
if (!servizio.onboardOk) {
|
if (!servizio.onboardOk) {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col items-center justify-center gap-8">
|
<div className="flex w-full flex-col items-center justify-center gap-8">
|
||||||
|
|
@ -376,6 +383,7 @@ const Main = () => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// se non ok acconto, mostra invito a completare pagamento acconto o ad attivare se acconto già inserito
|
||||||
if (!servizio.isOkAcconto || servizio.decorrenza === null) {
|
if (!servizio.isOkAcconto || servizio.decorrenza === null) {
|
||||||
const acconti = servizio.ordini.filter(
|
const acconti = servizio.ordini.filter(
|
||||||
(o) => o.type === OrderTypeEnum.Acconto,
|
(o) => o.type === OrderTypeEnum.Acconto,
|
||||||
|
|
@ -468,13 +476,15 @@ const Main = () => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//servizio scaduto se decorrenza + 60 giorni è passato e non è ok saldo
|
||||||
if (
|
if (
|
||||||
new Date() >
|
new Date() >
|
||||||
add(servizio.decorrenza, {
|
add(servizio.decorrenza, {
|
||||||
days: 60,
|
days: DEFAULT_SERVIZIO_DURATION_DAYS,
|
||||||
}) &&
|
}) &&
|
||||||
!servizio.isOkSaldo
|
!servizio.isOkSaldo
|
||||||
) {
|
) {
|
||||||
|
//todo capire come fare con saldo quando scade
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className="font-medium text-lg">{t.servizio.scaduto}</p>
|
<p className="font-medium text-lg">{t.servizio.scaduto}</p>
|
||||||
|
|
@ -482,10 +492,12 @@ const Main = () => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const annuncioConfermato = servizio.annunci.filter(
|
const annuncioConfermato = servizio.annunci.filter(
|
||||||
(a) => a.accettato_conferma_at !== null,
|
(a) => a.accettato_conferma_at !== null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// in conferma, mostra solo annunci confermati
|
||||||
if (servizio.isOkSaldo && annuncioConfermato.length > 0) {
|
if (servizio.isOkSaldo && annuncioConfermato.length > 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -503,6 +515,23 @@ const Main = () => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// se superato periodo interruzione, ma non interrotto, non scaduto e non in conferma, mostra avviso
|
||||||
|
if (
|
||||||
|
!servizio.acceptedInterr &&
|
||||||
|
isBefore(
|
||||||
|
new Date(),
|
||||||
|
add(servizio.decorrenza, { days: servizio.interruzioneDays }),
|
||||||
|
) &&
|
||||||
|
isAfter(
|
||||||
|
new Date(),
|
||||||
|
add(servizio.decorrenza, { days: servizio.interruzioneDays - 5 }),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return <DisclaimerInterruzioneServizio />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// normal case
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{annuncioConfermato.length > 0 && (
|
{annuncioConfermato.length > 0 && (
|
||||||
|
|
@ -511,31 +540,29 @@ const Main = () => {
|
||||||
<PackageCheck className="size-6 text-primary" />
|
<PackageCheck className="size-6 text-primary" />
|
||||||
<span className="font-medium">{t.servizio.saldi_in_attesa}</span>
|
<span className="font-medium">{t.servizio.saldi_in_attesa}</span>
|
||||||
</div>
|
</div>
|
||||||
{servizio.annunci
|
{annuncioConfermato.map((a) => (
|
||||||
.filter((a) => a.accettato_conferma_at !== null)
|
<div
|
||||||
.map((a) => (
|
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
|
||||||
<div
|
key={a.annunci_id}
|
||||||
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
|
>
|
||||||
key={a.annunci_id}
|
<h3>
|
||||||
>
|
{a.codice} -{" "}
|
||||||
<h3>
|
{a.accettato_conferma_at &&
|
||||||
{a.codice} -{" "}
|
new Date(a.accettato_conferma_at).toLocaleDateString("it", {
|
||||||
{a.accettato_conferma_at &&
|
day: "2-digit",
|
||||||
new Date(a.accettato_conferma_at).toLocaleDateString("it", {
|
hour: "2-digit",
|
||||||
day: "2-digit",
|
minute: "2-digit",
|
||||||
hour: "2-digit",
|
month: "2-digit",
|
||||||
minute: "2-digit",
|
year: "numeric",
|
||||||
month: "2-digit",
|
})}
|
||||||
year: "numeric",
|
</h3>
|
||||||
})}
|
<SaldoButton
|
||||||
</h3>
|
annuncioId={a.annunci_id}
|
||||||
<SaldoButton
|
className="border-none"
|
||||||
annuncioId={a.annunci_id}
|
variant={"outline"}
|
||||||
className="border-none"
|
/>
|
||||||
variant={"outline"}
|
</div>
|
||||||
/>
|
))}
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
|
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
|
||||||
|
|
@ -550,7 +577,7 @@ const Main = () => {
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{servizio.annunci.length % 2 === 1 && (
|
{servizio.annunci.length > 1 && servizio.annunci.length % 2 === 1 && (
|
||||||
<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 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>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
import { add, isBefore } from "date-fns";
|
|
||||||
import {
|
import {
|
||||||
Bug,
|
Bug,
|
||||||
Check,
|
Check,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ClockAlert,
|
|
||||||
Cog,
|
Cog,
|
||||||
Euro,
|
Euro,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Logs,
|
Logs,
|
||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
SlidersHorizontal,
|
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
@ -28,17 +25,8 @@ import {
|
||||||
} from "~/components/custom_ui/credenza";
|
} from "~/components/custom_ui/credenza";
|
||||||
import { MultiSelect } from "~/components/custom_ui/multiselect";
|
import { MultiSelect } from "~/components/custom_ui/multiselect";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import {
|
import { InterruzioneServizio } from "~/components/servizio/interruzione";
|
||||||
AlertDialog,
|
import { EditServizioModal } from "~/components/servizio/parametri_ricerca";
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
} from "~/components/ui/alert-dialog";
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
|
|
@ -54,17 +42,11 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "~/components/ui/dialog";
|
} from "~/components/ui/dialog";
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "~/components/ui/tooltip";
|
|
||||||
import {
|
import {
|
||||||
type FormValues as EditFormValues,
|
type FormValues as EditFormValues,
|
||||||
FormEditServizio,
|
FormEditServizio,
|
||||||
} from "~/forms/FormEditServizioAdmin";
|
} from "~/forms/FormEditServizioAdmin";
|
||||||
import { FormNewOrder } from "~/forms/FormNewOrdine";
|
import { FormNewOrder } from "~/forms/FormNewOrdine";
|
||||||
import { FormServizio, type FormValues } from "~/forms/FormServizio";
|
|
||||||
import { formatCurrency } from "~/lib/utils";
|
import { formatCurrency } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useServizio } from "~/providers/ServizioProvider";
|
import { useServizio } from "~/providers/ServizioProvider";
|
||||||
|
|
@ -118,7 +100,7 @@ const UserActions = () => {
|
||||||
if (isAdmin || canUserEdit) {
|
if (isAdmin || canUserEdit) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EditParametri />
|
<EditServizioModal />
|
||||||
<InterruzioneServizio />
|
<InterruzioneServizio />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -581,155 +563,6 @@ const AddAnnuncio = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const InterruzioneServizio = () => {
|
|
||||||
const { servizio, isAdmin } = useServizio();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const { mutate } = api.servizio.interruzioneServizio.useMutation({
|
|
||||||
onError: (error) => {
|
|
||||||
console.error(error);
|
|
||||||
toast.error("Errore durante la richiesta di interruzione");
|
|
||||||
},
|
|
||||||
onSuccess: async () => {
|
|
||||||
await utils.servizio.invalidate();
|
|
||||||
toast.success("Interruzione richiesta");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!servizio.decorrenza) return null;
|
|
||||||
const fineInterruzione = add(servizio.decorrenza, {
|
|
||||||
days: 14,
|
|
||||||
});
|
|
||||||
|
|
||||||
const canInterrompere =
|
|
||||||
!servizio.isInterrotto &&
|
|
||||||
(isAdmin ||
|
|
||||||
(isBefore(new Date(), fineInterruzione) &&
|
|
||||||
servizio.annunci.every((a) => a.accettato_conferma_at === null)));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AlertDialog>
|
|
||||||
<AlertDialogTrigger asChild>
|
|
||||||
{canInterrompere ? (
|
|
||||||
<Button
|
|
||||||
aria-label="Request Interruzione"
|
|
||||||
className="w-full"
|
|
||||||
disabled={!canInterrompere}
|
|
||||||
>
|
|
||||||
<ClockAlert /> <span>{t.servizio.interruzione.btn}</span>
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Tooltip delayDuration={0}>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span className="inline-block w-full">
|
|
||||||
<Button
|
|
||||||
aria-label="Request Interruzione"
|
|
||||||
className="w-full"
|
|
||||||
disabled={!canInterrompere}
|
|
||||||
>
|
|
||||||
<ClockAlert /> <span>{t.servizio.interruzione.btn}</span>
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>Periodo di interruzione scaduto, contattaci.</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</AlertDialogTrigger>
|
|
||||||
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>{t.servizio.interruzione.title}</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription className="space-y-2">
|
|
||||||
<p>
|
|
||||||
<b>
|
|
||||||
Fine periodo di interruzione:{" "}
|
|
||||||
{fineInterruzione.toLocaleDateString("it-IT")}
|
|
||||||
</b>
|
|
||||||
</p>
|
|
||||||
<p className="whitespace-pre-line">
|
|
||||||
{t.servizio.interruzione.description}
|
|
||||||
</p>
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>{t.annulla}</AlertDialogCancel>
|
|
||||||
<AlertDialogAction
|
|
||||||
aria-label="Confirm Interruzione"
|
|
||||||
onClick={() => mutate({ servizioId: servizio.servizio_id })}
|
|
||||||
>
|
|
||||||
{t.conferma}
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const EditParametri = () => {
|
|
||||||
const { servizioId, isAdmin, servizio } = useServizio();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [openEditParametri, setOpenEditParametri] = useState(false);
|
|
||||||
|
|
||||||
const utils = api.useUtils();
|
|
||||||
|
|
||||||
const { mutate: update } = api.servizio.updateServizio.useMutation({
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(error.message);
|
|
||||||
},
|
|
||||||
onSuccess: async () => {
|
|
||||||
toast.success("Servizio modificato con successo");
|
|
||||||
await utils.servizio.invalidate();
|
|
||||||
|
|
||||||
setOpenEditParametri(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function onSubmit(fields: FormValues) {
|
|
||||||
update({
|
|
||||||
data: {
|
|
||||||
...fields,
|
|
||||||
},
|
|
||||||
servizioId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Credenza onOpenChange={setOpenEditParametri} open={openEditParametri}>
|
|
||||||
<CredenzaTrigger asChild>
|
|
||||||
<Button
|
|
||||||
aria-label="Parametri di ricerca"
|
|
||||||
className="w-full"
|
|
||||||
disabled={
|
|
||||||
!isAdmin &&
|
|
||||||
(servizio.isInterrotto ||
|
|
||||||
servizio.annunci.some((a) => a.hasConfermaAdmin))
|
|
||||||
}
|
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
<SlidersHorizontal />
|
|
||||||
{t.servizio.parametri.btn}
|
|
||||||
</Button>
|
|
||||||
</CredenzaTrigger>
|
|
||||||
<CredenzaContent className="max-h-[90vh] w-full sm:max-w-5xl">
|
|
||||||
<CredenzaHeader>
|
|
||||||
<CredenzaTitle>{t.servizio.parametri.title}</CredenzaTitle>
|
|
||||||
<CredenzaDescription className="sr-only">
|
|
||||||
{t.servizio.parametri.title}
|
|
||||||
</CredenzaDescription>
|
|
||||||
</CredenzaHeader>
|
|
||||||
<CredenzaBody className="max-h-[80vh] w-full max-w-3xl overflow-y-auto pb-5 sm:max-w-5xl">
|
|
||||||
<FormServizio
|
|
||||||
initialData={servizio}
|
|
||||||
isLimited={!isAdmin}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
/>
|
|
||||||
</CredenzaBody>
|
|
||||||
</CredenzaContent>
|
|
||||||
</Credenza>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const EditServizioAdmin = () => {
|
const EditServizioAdmin = () => {
|
||||||
const { servizioId, userId, servizio } = useServizio();
|
const { servizioId, userId, servizio } = useServizio();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { format } from "date-fns";
|
||||||
import { enUS, it } from "date-fns/locale";
|
import { enUS, it } from "date-fns/locale";
|
||||||
import { CalendarIcon, X } from "lucide-react";
|
import { CalendarIcon, X } from "lucide-react";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
import { Counter } from "~/components/counter";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -24,6 +25,7 @@ import { cn } from "~/lib/utils";
|
||||||
import { useZodForm } from "~/lib/zodForm";
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type { Servizio } from "~/schemas/public/Servizio";
|
import type { Servizio } from "~/schemas/public/Servizio";
|
||||||
|
import { DEFAULT_SERVIZIO_INTERRUZIONE_DURATION_DAYS } from "~/utils/config";
|
||||||
|
|
||||||
const Schema = z.object({
|
const Schema = z.object({
|
||||||
decorrenza: z.date().nullable(),
|
decorrenza: z.date().nullable(),
|
||||||
|
|
@ -31,11 +33,12 @@ const Schema = z.object({
|
||||||
isOkAcconto: z.boolean(),
|
isOkAcconto: z.boolean(),
|
||||||
isOkConsulenza: z.boolean(),
|
isOkConsulenza: z.boolean(),
|
||||||
isOkSaldo: z.boolean(),
|
isOkSaldo: z.boolean(),
|
||||||
isSent: z.boolean(),
|
|
||||||
skipPayment: z.boolean(),
|
skipPayment: z.boolean(),
|
||||||
skipControlloDoc: z.boolean(),
|
skipControlloDoc: z.boolean(),
|
||||||
skipDocMotivazione: z.boolean(),
|
skipDocMotivazione: z.boolean(),
|
||||||
onboardOk: z.boolean(),
|
onboardOk: z.boolean(),
|
||||||
|
interruzioneDays: z.number(),
|
||||||
|
acceptedInterr: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormValues = z.infer<typeof Schema>;
|
export type FormValues = z.infer<typeof Schema>;
|
||||||
|
|
@ -61,11 +64,12 @@ export const FormEditServizio = ({
|
||||||
isOkAcconto: false,
|
isOkAcconto: false,
|
||||||
isOkConsulenza: false,
|
isOkConsulenza: false,
|
||||||
isOkSaldo: false,
|
isOkSaldo: false,
|
||||||
isSent: false,
|
|
||||||
skipPayment: false,
|
skipPayment: false,
|
||||||
skipControlloDoc: false,
|
skipControlloDoc: false,
|
||||||
skipDocMotivazione: false,
|
skipDocMotivazione: false,
|
||||||
onboardOk: false,
|
onboardOk: false,
|
||||||
|
interruzioneDays: DEFAULT_SERVIZIO_INTERRUZIONE_DURATION_DAYS,
|
||||||
|
acceptedInterr: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { locale, t } = useTranslation();
|
const { locale, t } = useTranslation();
|
||||||
|
|
@ -82,30 +86,6 @@ export const FormEditServizio = ({
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form className="space-y-8 px-0.5" onSubmit={form.handleSubmit(onSubmit)}>
|
<form className="space-y-8 px-0.5" onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
<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="isSent"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="w-full">
|
|
||||||
<div className="flex flex-wrap items-center gap-x-2">
|
|
||||||
<FormLabel htmlFor="isSent">Inviato</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
className="data-[state=checked]:bg-neutral-700"
|
|
||||||
defaultChecked={field.value}
|
|
||||||
id="isSent"
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormDescription>
|
|
||||||
La mail di benvenuto al cliente è stata inviata
|
|
||||||
</FormDescription>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="isInterrotto"
|
name="isInterrotto"
|
||||||
|
|
@ -128,6 +108,53 @@ export const FormEditServizio = ({
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="acceptedInterr"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-full">
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel htmlFor="acceptedInterr">
|
||||||
|
Informativa Interruzione Vista
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
className="data-[state=checked]:bg-neutral-700"
|
||||||
|
defaultChecked={field.value}
|
||||||
|
id="acceptedInterr"
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="interruzioneDays"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className={cn("w-full")}>
|
||||||
|
<div className="flex flex-wrap items-center gap-x-2">
|
||||||
|
<FormLabel
|
||||||
|
className={cn("font-semibold text-base")}
|
||||||
|
htmlFor="interruzioneDays"
|
||||||
|
>
|
||||||
|
Giorni interruzione
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Counter
|
||||||
|
name="interruzioneDays"
|
||||||
|
onlyPositive
|
||||||
|
setValue={(value) => field.onChange(Number(value))}
|
||||||
|
value={Number(field.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</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
|
<FormField
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,11 @@ import type { KyselyPlugin, UnknownRow } from "kysely";
|
||||||
import {
|
import {
|
||||||
createQueryId,
|
createQueryId,
|
||||||
DummyDriver,
|
DummyDriver,
|
||||||
type Insertable,
|
|
||||||
Kysely,
|
Kysely,
|
||||||
NoResultError,
|
NoResultError,
|
||||||
PostgresAdapter,
|
PostgresAdapter,
|
||||||
PostgresIntrospector,
|
PostgresIntrospector,
|
||||||
PostgresQueryCompiler,
|
PostgresQueryCompiler,
|
||||||
type Selectable,
|
|
||||||
type Updateable,
|
|
||||||
} from "kysely";
|
} from "kysely";
|
||||||
import { jsonArrayFrom } from "kysely/helpers/postgres";
|
import { jsonArrayFrom } from "kysely/helpers/postgres";
|
||||||
import { describe, expect, expectTypeOf, it } from "vitest";
|
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||||
|
|
@ -39,11 +36,6 @@ interface ItemTable {
|
||||||
updated_at: Date | null;
|
updated_at: Date | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Box = Selectable<BoxTable>;
|
|
||||||
export type Item = Selectable<ItemTable>;
|
|
||||||
export type NewItem = Insertable<ItemTable>;
|
|
||||||
export type ItemUpdate = Updateable<ItemTable>;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// DummyDriver db — used only for compile-time type tests
|
// DummyDriver db — used only for compile-time type tests
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import type { GetServerSideProps } from "next";
|
import type { GetServerSideProps } from "next";
|
||||||
import { AreaRiservataLayoutUserView } from "~/components/Layout";
|
import { AreaRiservataLayoutUserView } from "~/components/Layout";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { ServiziHeader } from "~/components/servizio/servizi_header";
|
import { NewServizioModal } from "~/components/servizio/parametri_ricerca";
|
||||||
|
import { NewRinnovoModal } from "~/components/servizio/rinnovo";
|
||||||
import { ServizioList } from "~/components/servizio/servizio";
|
import { ServizioList } from "~/components/servizio/servizio";
|
||||||
import { AnnunciRichiesti } from "~/components/servizio/servizio_annunci_accordions";
|
import { AnnunciRichiesti } from "~/components/servizio/servizio_annunci_accordions";
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
|
|
@ -27,7 +28,13 @@ const RicercaUser: NextPageWithLayout<RicercaUserProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<ServiziHeader userId={userId} />
|
<div className="flex w-full flex-wrap items-center justify-between gap-2">
|
||||||
|
<h3 className="font-semibold text-xl">Servizi cliente</h3>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<NewServizioModal userId={userId} />
|
||||||
|
<NewRinnovoModal userId={userId} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<AnnunciRichiesti userId={userId} />
|
<AnnunciRichiesti userId={userId} />
|
||||||
<ServizioList data={data || []} isAdmin userId={userId} />
|
<ServizioList data={data || []} isAdmin userId={userId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,6 @@ export default interface ServizioTable {
|
||||||
|
|
||||||
reddito: ColumnType<string | null, string | null, string | null>;
|
reddito: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
isSent: ColumnType<boolean, boolean | undefined, boolean>;
|
|
||||||
|
|
||||||
decorrenza: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
decorrenza: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||||
|
|
||||||
isInterrotto: ColumnType<boolean, boolean | undefined, boolean>;
|
isInterrotto: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
@ -72,6 +70,10 @@ export default interface ServizioTable {
|
||||||
skipDocMotivazione: ColumnType<boolean, boolean | undefined, boolean>;
|
skipDocMotivazione: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
onboardOk: ColumnType<boolean, boolean | undefined, boolean>;
|
onboardOk: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
|
interruzioneDays: ColumnType<number, number | undefined, number>;
|
||||||
|
|
||||||
|
acceptedInterr: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Servizio = Selectable<ServizioTable>;
|
export type Servizio = Selectable<ServizioTable>;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Override } from "TypeHelpers.type";
|
import type { Override } from "TypeHelpers.type";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { add } from "date-fns";
|
import { add, isBefore } from "date-fns";
|
||||||
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
|
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import type { PurchaseData } from "~/components/acquisto_receipt";
|
import type { PurchaseData } from "~/components/acquisto_receipt";
|
||||||
|
|
@ -224,14 +224,20 @@ export const attivaServizio_SaltaPagamentoAcconto = async (
|
||||||
() => new Error(`Ordine not found - ordineId: ${ordineId}`),
|
() => new Error(`Ordine not found - ordineId: ${ordineId}`),
|
||||||
);
|
);
|
||||||
|
|
||||||
await tx
|
const servizio = await tx
|
||||||
.updateTable("servizio")
|
.updateTable("servizio")
|
||||||
.set({
|
.set({
|
||||||
decorrenza: new Date(),
|
decorrenza: new Date(),
|
||||||
isOkAcconto: true,
|
isOkAcconto: true,
|
||||||
})
|
})
|
||||||
.where("servizio_id", "=", ordine.servizio_id)
|
.where("servizio_id", "=", ordine.servizio_id)
|
||||||
.execute();
|
.returning(["interruzioneDays"])
|
||||||
|
.executeTakeFirstOrThrow(
|
||||||
|
() =>
|
||||||
|
new Error(
|
||||||
|
`Failed to update servizio - servizioId: ${ordine.servizio_id}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
await NewMail({
|
await NewMail({
|
||||||
template: {
|
template: {
|
||||||
|
|
@ -271,7 +277,7 @@ export const attivaServizio_SaltaPagamentoAcconto = async (
|
||||||
userId: ordine.userId,
|
userId: ordine.userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lock_expires: add(new Date(), { days: 10 }),
|
lock_expires: add(new Date(), { days: servizio.interruzioneDays - 5 }),
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
@ -290,7 +296,7 @@ export const AdminAttivaServizio_Ufficio = async (
|
||||||
return await db.transaction().execute(async (tx) => {
|
return await db.transaction().execute(async (tx) => {
|
||||||
const data = await tx
|
const data = await tx
|
||||||
.selectFrom("servizio")
|
.selectFrom("servizio")
|
||||||
.select("servizio.user_id")
|
.select(["servizio.user_id", "servizio.interruzioneDays"])
|
||||||
.innerJoin("users", "users.id", "servizio.user_id")
|
.innerJoin("users", "users.id", "servizio.user_id")
|
||||||
.select("users.email")
|
.select("users.email")
|
||||||
.where("servizio_id", "=", servizioId)
|
.where("servizio_id", "=", servizioId)
|
||||||
|
|
@ -322,7 +328,7 @@ export const AdminAttivaServizio_Ufficio = async (
|
||||||
userId: data.user_id,
|
userId: data.user_id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lock_expires: add(today, { days: 10 }),
|
lock_expires: add(today, { days: data.interruzioneDays - 5 }), //avviso 5 giorni prima della scadenza del periodo di ripensamento
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
@ -908,11 +914,12 @@ export const interruzioneServizio = async (servizioId: ServizioServizioId) => {
|
||||||
message: "Servizio decorrenza not set",
|
message: "Servizio decorrenza not set",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const isWithinRipensamento =
|
const isWithinRipensamento = isBefore(
|
||||||
new Date() <=
|
new Date(),
|
||||||
add(servizio.decorrenza, {
|
add(servizio.decorrenza, {
|
||||||
days: 14,
|
days: servizio.interruzioneDays,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (!isWithinRipensamento) {
|
if (!isWithinRipensamento) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,8 @@ export const whIntentSucceededHandler = async ({
|
||||||
db: trx,
|
db: trx,
|
||||||
userId: ordine.userid,
|
userId: ordine.userid,
|
||||||
});
|
});
|
||||||
const lock_expires = new Date(Date.now() + 3000); // Lock for 30 sec
|
|
||||||
|
const lock_expires = add(new Date(), { seconds: 30 }); // Lock for 30 sec
|
||||||
|
|
||||||
switch (ordine.type) {
|
switch (ordine.type) {
|
||||||
case OrderTypeEnum.Acconto: {
|
case OrderTypeEnum.Acconto: {
|
||||||
|
|
@ -108,14 +109,20 @@ export const whIntentSucceededHandler = async ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await trx
|
const servizio = await trx
|
||||||
.updateTable("servizio")
|
.updateTable("servizio")
|
||||||
.where("servizio_id", "=", ordine.servizio_id)
|
.where("servizio_id", "=", ordine.servizio_id)
|
||||||
.set({
|
.set({
|
||||||
decorrenza: new Date(),
|
decorrenza: new Date(),
|
||||||
isOkAcconto: true,
|
isOkAcconto: true,
|
||||||
})
|
})
|
||||||
.execute();
|
.returning(["interruzioneDays"])
|
||||||
|
.executeTakeFirstOrThrow(
|
||||||
|
() =>
|
||||||
|
new Error(
|
||||||
|
`Failed to update servizio - servizioId: ${ordine.servizio_id}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
await addEventToQueue({
|
await addEventToQueue({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -163,13 +170,15 @@ export const whIntentSucceededHandler = async ({
|
||||||
mailType: "avvisoInterruzione",
|
mailType: "avvisoInterruzione",
|
||||||
},
|
},
|
||||||
mail: {
|
mail: {
|
||||||
subject: "Avviso Interruzione",
|
subject: "Promemoria durata servizio - Infoalloggi.it",
|
||||||
to: utente.email,
|
to: utente.email,
|
||||||
},
|
},
|
||||||
userId: utente.id,
|
userId: utente.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lock_expires: add(new Date(), { days: 10 }),
|
lock_expires: add(new Date(), {
|
||||||
|
days: servizio.interruzioneDays - 5,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
//SERVIZIO ATTIVATO
|
//SERVIZIO ATTIVATO
|
||||||
|
|
||||||
|
|
@ -305,35 +314,6 @@ export const whIntentSucceededHandler = async ({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OrderTypeEnum.Altro:
|
case OrderTypeEnum.Altro:
|
||||||
// Handle "Altro" type if needed
|
|
||||||
// await addEventToQueue({
|
|
||||||
// data: {
|
|
||||||
// tipo: "email",
|
|
||||||
// data: {
|
|
||||||
// template: {
|
|
||||||
// mailType: "pagamentoConferma",
|
|
||||||
// },
|
|
||||||
// mail: {
|
|
||||||
// subject: "Pagamento Confermato - Infoalloggi.it",
|
|
||||||
// to: utente.email,
|
|
||||||
// attachments: [
|
|
||||||
// {
|
|
||||||
// filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
|
||||||
|
|
||||||
// generate: {
|
|
||||||
// type: "ricevuta_cortesia",
|
|
||||||
// ordineId: ordine.ordine_id,
|
|
||||||
// },
|
|
||||||
// encoding: "base64",
|
|
||||||
// contentType: "application/pdf",
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// userId: utente.id,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// lock_expires,
|
|
||||||
// });
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
await addEventToQueue({
|
await addEventToQueue({
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,19 @@ export const getEmails = async ({ userId }: { userId: UsersId }) => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const obsoleteMails = gen.filter((mail) => mail.html === "");
|
||||||
|
|
||||||
|
if (obsoleteMails.length > 0) {
|
||||||
|
await db
|
||||||
|
.deleteFrom("emails")
|
||||||
|
.where(
|
||||||
|
"id_email",
|
||||||
|
"in",
|
||||||
|
obsoleteMails.map((mail) => mail.id_email),
|
||||||
|
)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
return gen;
|
return gen;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|
|
||||||
|
|
@ -183,15 +183,12 @@ export const genMail = async ({ ...mail }: MailsTemplates) => {
|
||||||
title = "Servizio Attivato";
|
title = "Servizio Attivato";
|
||||||
break;
|
break;
|
||||||
case "avvisoInterruzione":
|
case "avvisoInterruzione":
|
||||||
//TODO da fare
|
|
||||||
mailComponent = AvvisoInterruzione();
|
mailComponent = AvvisoInterruzione();
|
||||||
title = "Avviso Interruzione";
|
title = "Avviso Interruzione";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new TRPCError({
|
console.error(`Tipo di email non valido: ${mail satisfies never}`);
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
return { html: "", title: "Email non valido/modello obsoleto" };
|
||||||
message: `Tipo di email non valido: ${mail satisfies never}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { html: await render(mailComponent), title };
|
return { html: await render(mailComponent), title };
|
||||||
|
|
@ -206,6 +203,11 @@ export const NewMail = async ({ template, userId, mail }: NewMailProps) => {
|
||||||
try {
|
try {
|
||||||
if (template) {
|
if (template) {
|
||||||
const { html } = await genMail({ ...template });
|
const { html } = await genMail({ ...template });
|
||||||
|
if (!html || html.trim() === "") {
|
||||||
|
throw new Error(
|
||||||
|
`Il template per il tipo ${template.mailType} è obsoleto e non genera più l'html. Email non inviata.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
await mailer({
|
await mailer({
|
||||||
html,
|
html,
|
||||||
...mail,
|
...mail,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue