2026-02-25 15:30:53 +01:00
|
|
|
import { add } from "date-fns";
|
|
|
|
|
import {
|
|
|
|
|
ArrowRight,
|
2026-03-08 01:02:57 +01:00
|
|
|
BadgeCheck,
|
2026-02-25 15:30:53 +01:00
|
|
|
CalendarClock,
|
|
|
|
|
CircleCheck,
|
|
|
|
|
CircleCheckBig,
|
|
|
|
|
CircleMinus,
|
2026-03-06 18:54:48 +01:00
|
|
|
Clock,
|
2026-02-25 15:30:53 +01:00
|
|
|
ClockFading,
|
2026-03-08 01:02:57 +01:00
|
|
|
ExternalLink,
|
2026-02-25 15:30:53 +01:00
|
|
|
type LucideIcon,
|
2026-03-12 13:48:45 +01:00
|
|
|
MousePointerClick,
|
2026-02-25 15:30:53 +01:00
|
|
|
PackageCheck,
|
|
|
|
|
Trash2,
|
|
|
|
|
} from "lucide-react";
|
|
|
|
|
import Link from "next/link";
|
|
|
|
|
import toast from "react-hot-toast";
|
|
|
|
|
import { cn } from "~/lib/utils";
|
|
|
|
|
import { useTranslation } from "~/providers/I18nProvider";
|
|
|
|
|
import {
|
|
|
|
|
ServizioAnnuncioProvider,
|
|
|
|
|
ServizioProvider,
|
|
|
|
|
useServizio,
|
|
|
|
|
} from "~/providers/ServizioProvider";
|
2026-03-08 01:02:57 +01:00
|
|
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
2026-03-12 12:11:57 +01:00
|
|
|
import type { Rinnovi } from "~/schemas/public/Rinnovi";
|
2026-02-25 15:30:53 +01:00
|
|
|
import type { UsersId } from "~/schemas/public/Users";
|
|
|
|
|
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
|
|
|
|
import { api } from "~/utils/api";
|
|
|
|
|
import { Confirm } from "../confirm";
|
|
|
|
|
import { AlarmClockSVG } from "../svgs";
|
2026-03-08 01:02:57 +01:00
|
|
|
import {
|
|
|
|
|
AlertDialog,
|
|
|
|
|
AlertDialogAction,
|
|
|
|
|
AlertDialogCancel,
|
|
|
|
|
AlertDialogContent,
|
|
|
|
|
AlertDialogDescription,
|
|
|
|
|
AlertDialogFooter,
|
|
|
|
|
AlertDialogHeader,
|
|
|
|
|
AlertDialogTitle,
|
|
|
|
|
AlertDialogTrigger,
|
|
|
|
|
} from "../ui/alert-dialog";
|
2026-02-25 15:30:53 +01:00
|
|
|
import { Button } from "../ui/button";
|
|
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from "../ui/card";
|
|
|
|
|
import { SaldoButton } from "./annuncio_actions";
|
|
|
|
|
import { AnnuncioCard, AnnuncioDisplay } from "./annuncio_card";
|
|
|
|
|
import { AnnunciCompatibili } from "./compatibili_dialog";
|
2026-03-08 01:02:57 +01:00
|
|
|
import { ServizioInfos } from "./servizio_actions";
|
2026-02-25 15:30:53 +01:00
|
|
|
|
|
|
|
|
export const ServizioList = ({
|
2026-03-12 12:11:57 +01:00
|
|
|
data,
|
2026-02-25 15:30:53 +01:00
|
|
|
isAdmin,
|
|
|
|
|
userId,
|
|
|
|
|
}: {
|
2026-03-12 12:11:57 +01:00
|
|
|
data: (
|
|
|
|
|
| { d: Date; type: "servizio"; data: ServizioData }
|
|
|
|
|
| { d: Date; type: "rinnovo"; data: Rinnovi }
|
|
|
|
|
)[];
|
2026-02-25 15:30:53 +01:00
|
|
|
isAdmin: boolean;
|
|
|
|
|
userId: UsersId;
|
|
|
|
|
}) => {
|
|
|
|
|
const { t } = useTranslation();
|
2026-03-12 12:11:57 +01:00
|
|
|
if (data.length === 0) {
|
2026-02-25 15:30:53 +01:00
|
|
|
return (
|
|
|
|
|
<div className="m-4 rounded-md bg-yellow-100 p-4 text-yellow-900">
|
|
|
|
|
{t.servizio.nessun_servizio}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-03-12 12:11:57 +01:00
|
|
|
|
2026-02-25 15:30:53 +01:00
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col gap-4">
|
2026-03-12 12:11:57 +01:00
|
|
|
{data.map(({ d: _d, type, data: s }) => {
|
|
|
|
|
if (type === "rinnovo") {
|
|
|
|
|
return (
|
|
|
|
|
<RinnovoLinkCard
|
|
|
|
|
data={s}
|
|
|
|
|
isAdmin={isAdmin}
|
|
|
|
|
key={s.id}
|
|
|
|
|
userId={userId}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<ServizioLinkCard
|
|
|
|
|
isAdmin={isAdmin}
|
|
|
|
|
key={s.servizio_id}
|
|
|
|
|
servizio={s}
|
|
|
|
|
userId={userId}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})}
|
2026-02-25 15:30:53 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-12 12:11:57 +01:00
|
|
|
const RinnovoLinkCard = ({
|
|
|
|
|
data,
|
|
|
|
|
isAdmin,
|
|
|
|
|
userId,
|
|
|
|
|
}: {
|
|
|
|
|
data: Rinnovi;
|
|
|
|
|
isAdmin: boolean;
|
|
|
|
|
userId: UsersId;
|
|
|
|
|
}) => {
|
|
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
className="group"
|
|
|
|
|
href={
|
|
|
|
|
isAdmin
|
|
|
|
|
? `/area-riservata/admin/user-view/rinnovo/${userId}/${data.id}`
|
|
|
|
|
: `/area-riservata/rinnovo/${data.id}`
|
|
|
|
|
}
|
|
|
|
|
key={data.id}
|
|
|
|
|
>
|
|
|
|
|
<Card
|
2026-03-12 16:27:24 +01:00
|
|
|
className={cn("transition-shadow hover:bg-muted/15 hover:shadow-md")}
|
2026-03-12 12:11:57 +01:00
|
|
|
>
|
|
|
|
|
<CardHeader>
|
2026-03-19 12:14:11 +01:00
|
|
|
<CardTitle className="text-xl group-hover:underline">
|
2026-03-12 12:11:57 +01:00
|
|
|
Rinnovo {data.codice}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription className="">
|
|
|
|
|
Data rinnovo: {data.decorrenza.toLocaleDateString("it-IT")}
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="flex flex-col flex-wrap gap-4 sm:flex-row sm:justify-between">
|
|
|
|
|
<div className="flex items-center gap-2 rounded-md text-muted-foreground group-hover:text-foreground">
|
|
|
|
|
<span>Vai al rinnovo</span>
|
|
|
|
|
<ArrowRight />
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-25 15:30:53 +01:00
|
|
|
const ServizioLinkCard = ({
|
|
|
|
|
servizio,
|
|
|
|
|
isAdmin,
|
|
|
|
|
userId,
|
|
|
|
|
}: {
|
|
|
|
|
servizio: ServizioData;
|
|
|
|
|
isAdmin: boolean;
|
|
|
|
|
userId: UsersId;
|
|
|
|
|
}) => {
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
let status: keyof typeof t.servizio.status = "attivo";
|
|
|
|
|
let StatusIcon: LucideIcon = CircleCheck;
|
|
|
|
|
|
|
|
|
|
if (servizio.isInterrotto) {
|
|
|
|
|
status = "interrotto";
|
|
|
|
|
StatusIcon = Trash2;
|
|
|
|
|
} else if (!servizio.isOkAcconto) {
|
|
|
|
|
status = "in_attesa";
|
|
|
|
|
StatusIcon = ClockFading;
|
|
|
|
|
} else if (servizio.decorrenza) {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const decorrenza = new Date(servizio.decorrenza);
|
|
|
|
|
if (now > add(decorrenza, { days: 60 }) && !servizio.isOkSaldo) {
|
|
|
|
|
status = "scaduto";
|
|
|
|
|
StatusIcon = CircleMinus;
|
|
|
|
|
}
|
|
|
|
|
if (servizio.isOkSaldo) {
|
|
|
|
|
status = "completato";
|
|
|
|
|
StatusIcon = CircleCheckBig;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
className="group"
|
|
|
|
|
href={
|
|
|
|
|
isAdmin
|
|
|
|
|
? `/area-riservata/admin/user-view/servizio/${userId}/${servizio.servizio_id}`
|
|
|
|
|
: `/area-riservata/servizio/${servizio.servizio_id}`
|
|
|
|
|
}
|
|
|
|
|
key={servizio.servizio_id}
|
|
|
|
|
>
|
2026-03-19 12:14:11 +01:00
|
|
|
{status === "in_attesa" ? (
|
|
|
|
|
<Card className="relative border-yellow-500 bg-yellow-500 text-white hover:bg-yellow-500/90 dark:bg-yellow-900 dark:hover:bg-yellow-900/90">
|
|
|
|
|
<span className="absolute top-5 right-5 -mt-1 -mr-1 flex size-7 items-center justify-center">
|
|
|
|
|
<span className="absolute inline-flex size-6 h-full w-full animate-ping rounded-full bg-white opacity-75" />
|
|
|
|
|
<span className="relative inline-flex size-5 rounded-full bg-white"></span>
|
|
|
|
|
</span>
|
2026-02-25 15:30:53 +01:00
|
|
|
|
2026-03-19 12:14:11 +01:00
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="text-xl group-hover:underline">
|
|
|
|
|
{t.servizio.servizio_titolo} {servizio.tipologia}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription className="text-white">
|
|
|
|
|
{t.servizio.servizio_creato_il}{" "}
|
|
|
|
|
{servizio.created_at.toLocaleDateString("it-IT")}
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="flex flex-col flex-wrap gap-4 sm:flex-row sm:justify-between">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<StatusIcon className="size-5" />
|
|
|
|
|
|
|
|
|
|
<span>{t.servizio.status[status]}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-2 rounded-md font-semibold text-white underline underline-offset-2">
|
|
|
|
|
<span>{t.servizio.vai_al_servizio}</span>
|
|
|
|
|
<MousePointerClick />
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
) : (
|
|
|
|
|
<Card
|
|
|
|
|
className={cn(
|
|
|
|
|
status === "attivo" && "border-green-500",
|
|
|
|
|
status === "interrotto" && "border-red-500",
|
|
|
|
|
status === "scaduto" && "border-gray-500",
|
|
|
|
|
status === "completato" && "border-blue-500",
|
|
|
|
|
"transition-shadow hover:bg-muted/15 hover:shadow-md",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="text-xl group-hover:underline">
|
|
|
|
|
{t.servizio.servizio_titolo} {servizio.tipologia}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription className="">
|
|
|
|
|
{t.servizio.servizio_creato_il}{" "}
|
|
|
|
|
{servizio.created_at.toLocaleDateString("it-IT")}
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="flex flex-col flex-wrap gap-4 sm:flex-row sm:justify-between">
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2",
|
|
|
|
|
status === "attivo" && "text-green-500",
|
|
|
|
|
status === "interrotto" && "text-red-500",
|
|
|
|
|
status === "scaduto" && "text-gray-500",
|
|
|
|
|
status === "completato" && "text-blue-500",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<StatusIcon className="size-5" />
|
|
|
|
|
|
|
|
|
|
<span>{t.servizio.status[status]}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-2 rounded-md font-semibold text-muted-foreground group-hover:text-foreground">
|
|
|
|
|
<span>{t.servizio.vai_al_servizio}</span>
|
|
|
|
|
<MousePointerClick />
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)}
|
2026-02-25 15:30:53 +01:00
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const Servizio = ({
|
|
|
|
|
servizio,
|
|
|
|
|
userId,
|
|
|
|
|
isAdmin,
|
|
|
|
|
}: {
|
|
|
|
|
servizio: ServizioData;
|
|
|
|
|
userId: UsersId;
|
|
|
|
|
isAdmin: boolean;
|
|
|
|
|
}) => {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioProvider
|
|
|
|
|
isAdmin={isAdmin}
|
|
|
|
|
servizio={servizio}
|
|
|
|
|
servizioId={servizio.servizio_id}
|
|
|
|
|
userId={userId}
|
|
|
|
|
>
|
|
|
|
|
<Content />
|
|
|
|
|
</ServizioProvider>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Content = () => {
|
|
|
|
|
const { servizio } = useServizio();
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const dec_txt = servizio.decorrenza
|
|
|
|
|
? new Date(servizio.decorrenza).toLocaleDateString("it-IT")
|
2026-03-08 01:02:57 +01:00
|
|
|
: "";
|
2026-02-25 15:30:53 +01:00
|
|
|
const end_txt = servizio.decorrenza
|
|
|
|
|
? new Date(add(servizio.decorrenza, { days: 60 })).toLocaleDateString(
|
|
|
|
|
"it-IT",
|
|
|
|
|
)
|
2026-03-08 01:02:57 +01:00
|
|
|
: "";
|
2026-02-25 15:30:53 +01:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="w-full space-y-4">
|
|
|
|
|
<div className="flex flex-col flex-wrap items-start justify-between gap-4 sm:flex-row">
|
2026-03-06 18:54:48 +01:00
|
|
|
<div className="flex flex-col gap-1">
|
2026-03-08 01:02:57 +01:00
|
|
|
<h3 className="font-semibold text-2xl">
|
2026-02-25 15:30:53 +01:00
|
|
|
{t.servizio.servizio_titolo} {servizio.tipologia}
|
|
|
|
|
</h3>
|
|
|
|
|
|
2026-03-06 18:54:48 +01:00
|
|
|
<div className="flex items-center gap-2 text-muted-foreground text-sm">
|
|
|
|
|
<Clock className="size-5" />
|
|
|
|
|
<span>
|
|
|
|
|
Creato il: {servizio.created_at.toLocaleDateString("it-IT")}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-25 15:30:53 +01:00
|
|
|
<div className="flex flex-wrap items-center gap-2 text-muted-foreground text-sm">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<CalendarClock className="size-5" />
|
|
|
|
|
<span>{t.servizio.durata_servizio}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<span>
|
2026-03-08 01:02:57 +01:00
|
|
|
{servizio.decorrenza !== null
|
|
|
|
|
? `${dec_txt} - ${end_txt}`
|
|
|
|
|
: "In attesa di attivazione"}
|
2026-02-25 15:30:53 +01:00
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-08 01:02:57 +01:00
|
|
|
<ServizioInfos />
|
2026-02-25 15:30:53 +01:00
|
|
|
</div>
|
2026-03-08 01:02:57 +01:00
|
|
|
|
2026-02-25 15:30:53 +01:00
|
|
|
<Main />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Main = () => {
|
2026-03-08 01:02:57 +01:00
|
|
|
const { servizio, isAdmin } = useServizio();
|
2026-02-25 15:30:53 +01:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const utils = api.useUtils();
|
2026-03-08 01:02:57 +01:00
|
|
|
const { mutate: attivazioneSenzaPagamento } =
|
|
|
|
|
api.servizio.attivazioneSenzaPagamento.useMutation({
|
|
|
|
|
onError: () => {
|
|
|
|
|
toast.error("Errore durante l'attivazione del servizio");
|
2026-02-25 15:30:53 +01:00
|
|
|
},
|
|
|
|
|
onSuccess: async () => {
|
2026-03-08 01:02:57 +01:00
|
|
|
toast.success("Servizio modificato con successo");
|
|
|
|
|
await utils.servizio.invalidate();
|
2026-02-25 15:30:53 +01:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (servizio.isInterrotto) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<p className="font-medium text-lg">{t.servizio.servizio_interrotto}</p>
|
|
|
|
|
<p>{t.servizio.servizio_interrotto_desc}</p>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-03-08 01:02:57 +01:00
|
|
|
if (!servizio.onboardOk) {
|
2026-02-25 15:30:53 +01:00
|
|
|
return (
|
2026-03-08 01:02:57 +01:00
|
|
|
<div className="flex w-full flex-col items-center justify-center gap-8">
|
2026-02-25 15:30:53 +01:00
|
|
|
<div className="flex w-full max-w-md flex-col items-center justify-center gap-4 text-center">
|
|
|
|
|
<AlarmClockSVG className="size-46" />
|
|
|
|
|
<h1 className="text-xl">{t.servizio.non_attivo}</h1>
|
|
|
|
|
<Link
|
|
|
|
|
aria-label="Attiva Servizio"
|
|
|
|
|
className="w-full"
|
2026-03-08 01:02:57 +01:00
|
|
|
href={
|
|
|
|
|
isAdmin
|
|
|
|
|
? `/area-riservata/admin/user-view/onboard/${servizio.servizio_id}`
|
|
|
|
|
: `/servizio/onboard/${servizio.servizio_id}`
|
|
|
|
|
}
|
2026-02-25 15:30:53 +01:00
|
|
|
>
|
|
|
|
|
<Button className="w-full text-lg" variant="success">
|
|
|
|
|
<span>{t.servizio.attiva_cta}</span>
|
|
|
|
|
<ArrowRight />
|
|
|
|
|
</Button>
|
|
|
|
|
</Link>
|
2026-03-08 01:02:57 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<AnnunciAnteprima />
|
2026-03-11 12:07:19 +01:00
|
|
|
{isAdmin && <AnnunciCompatibili />}
|
2026-03-08 01:02:57 +01:00
|
|
|
</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>
|
2026-02-25 15:30:53 +01:00
|
|
|
|
2026-03-08 01:02:57 +01:00
|
|
|
{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"
|
2026-02-25 15:30:53 +01:00
|
|
|
className="w-full"
|
2026-03-17 18:46:43 +01:00
|
|
|
href={`/servizio/pagamento?servizioId=${servizio.servizio_id}&type=${OrderTypeEnum.Acconto}`}
|
2026-02-25 15:30:53 +01:00
|
|
|
>
|
2026-03-08 01:02:57 +01:00
|
|
|
<Button className="w-full text-lg" variant="info">
|
|
|
|
|
<span>Procedi al pagamento</span>
|
|
|
|
|
<ArrowRight />
|
|
|
|
|
</Button>
|
|
|
|
|
</Link>
|
2026-02-25 15:30:53 +01:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-08 01:02:57 +01:00
|
|
|
<AnnunciAnteprima />
|
2026-03-11 12:07:19 +01:00
|
|
|
{isAdmin && <AnnunciCompatibili />}
|
2026-02-25 15:30:53 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
new Date() >
|
|
|
|
|
add(servizio.decorrenza, {
|
|
|
|
|
days: 60,
|
|
|
|
|
}) &&
|
|
|
|
|
!servizio.isOkSaldo
|
|
|
|
|
) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<p className="font-medium text-lg">{t.servizio.scaduto}</p>
|
|
|
|
|
<p>{t.servizio.scaduto_desc}</p>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const annuncioConfermato = servizio.annunci.filter(
|
|
|
|
|
(a) => a.accettato_conferma_at !== null,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (servizio.isOkSaldo && annuncioConfermato.length > 0) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{annuncioConfermato.map((data) => {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioAnnuncioProvider
|
|
|
|
|
data={data}
|
|
|
|
|
key={data.id}
|
|
|
|
|
servizioId={servizio.servizio_id}
|
|
|
|
|
>
|
|
|
|
|
<AnnuncioCard className="max-w-full" />
|
|
|
|
|
</ServizioAnnuncioProvider>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{annuncioConfermato.length > 0 && (
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<PackageCheck className="size-6 text-primary" />
|
|
|
|
|
<span className="font-medium">{t.servizio.saldi_in_attesa}</span>
|
|
|
|
|
</div>
|
|
|
|
|
{servizio.annunci
|
|
|
|
|
.filter((a) => a.accettato_conferma_at !== null)
|
|
|
|
|
.map((a) => (
|
|
|
|
|
<div
|
|
|
|
|
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
|
|
|
|
|
key={a.annunci_id}
|
|
|
|
|
>
|
|
|
|
|
<h3>
|
|
|
|
|
{a.codice} -{" "}
|
|
|
|
|
{a.accettato_conferma_at &&
|
|
|
|
|
new Date(a.accettato_conferma_at).toLocaleDateString("it", {
|
|
|
|
|
day: "2-digit",
|
|
|
|
|
hour: "2-digit",
|
|
|
|
|
minute: "2-digit",
|
|
|
|
|
month: "2-digit",
|
|
|
|
|
year: "numeric",
|
|
|
|
|
})}
|
|
|
|
|
</h3>
|
|
|
|
|
<SaldoButton
|
|
|
|
|
annuncioId={a.annunci_id}
|
|
|
|
|
className="border-none"
|
|
|
|
|
variant={"outline"}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-03-08 01:02:57 +01:00
|
|
|
<div className="@container grid grid-flow-row @sm:grid-cols-2 grid-cols-1 gap-4">
|
2026-02-25 15:30:53 +01:00
|
|
|
{servizio.annunci.map((data) => {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioAnnuncioProvider
|
|
|
|
|
data={data}
|
|
|
|
|
key={data.id}
|
|
|
|
|
servizioId={servizio.servizio_id}
|
|
|
|
|
>
|
|
|
|
|
<AnnuncioCard />
|
|
|
|
|
</ServizioAnnuncioProvider>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
{servizio.annunci.length % 2 === 1 && (
|
2026-03-08 01:02:57 +01:00
|
|
|
<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]" />
|
2026-02-25 15:30:53 +01:00
|
|
|
)}
|
|
|
|
|
</div>
|
2026-03-08 01:02:57 +01:00
|
|
|
<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>
|
|
|
|
|
)}
|
2026-02-25 15:30:53 +01:00
|
|
|
<AnnunciCompatibili />
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
2026-03-08 01:02:57 +01:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
};
|