2025-10-21 14:35:06 +02:00
|
|
|
import { ExternalLink, Package, PackageCheck, UserCircle } from "lucide-react";
|
2025-08-04 17:45:44 +02:00
|
|
|
import Link from "next/link";
|
|
|
|
|
import { useRouter } from "next/router";
|
2025-10-21 14:35:06 +02:00
|
|
|
import { type ReactNode, useEffect } from "react";
|
2025-08-04 17:45:44 +02:00
|
|
|
import toast from "react-hot-toast";
|
2025-10-21 14:35:06 +02:00
|
|
|
import { InteressatoButtonBatchV } from "~/components/annuncio-interactions/annuncio_interactions";
|
|
|
|
|
import { AddButton } from "~/components/servizio/annuncio_actions";
|
|
|
|
|
import {
|
|
|
|
|
AnnuncioCard,
|
|
|
|
|
BasicAnnuncioCard,
|
|
|
|
|
} from "~/components/servizio/annuncio_card";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { SaldoButton } from "~/components/servizio/interactions";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { ServizioDuration } from "~/components/servizio/service-duration-display";
|
2025-08-28 18:27:07 +02:00
|
|
|
import {
|
|
|
|
|
ServizioActions,
|
|
|
|
|
ServizioActions2,
|
|
|
|
|
} from "~/components/servizio/servizio_actions";
|
|
|
|
|
import {
|
|
|
|
|
AnnunciCompatibili,
|
|
|
|
|
AnnunciInConferma,
|
|
|
|
|
AnnunciSelezionati,
|
|
|
|
|
} from "~/components/servizio/servizio_annunci_accordions";
|
|
|
|
|
import { Button } from "~/components/ui/button";
|
|
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from "~/components/ui/card";
|
2025-10-21 14:35:06 +02:00
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogTrigger,
|
|
|
|
|
} from "~/components/ui/dialog";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { Progress } from "~/components/ui/progress";
|
|
|
|
|
import { cn } from "~/lib/utils";
|
|
|
|
|
import {
|
|
|
|
|
ServizioAnnuncioProvider,
|
|
|
|
|
ServizioProvider,
|
|
|
|
|
useServizio,
|
|
|
|
|
} from "~/providers/ServizioProvider";
|
|
|
|
|
import type { UsersId } from "~/schemas/public/Users";
|
|
|
|
|
import type { ServizioData } from "~/server/controllers/servizio.controller";
|
|
|
|
|
import { api } from "~/utils/api";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
export const ServizioContainer = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
servizio,
|
|
|
|
|
userId,
|
|
|
|
|
isAdmin,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
servizio: ServizioData;
|
|
|
|
|
userId: UsersId;
|
|
|
|
|
isAdmin: boolean;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<ServizioProvider
|
|
|
|
|
isAdmin={isAdmin}
|
|
|
|
|
servizio={servizio}
|
2025-08-29 16:18:32 +02:00
|
|
|
servizioId={servizio.servizio_id}
|
|
|
|
|
userId={userId}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<ServizioMain />
|
|
|
|
|
</ServizioProvider>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ServizioCard = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
cardClassName,
|
|
|
|
|
header,
|
|
|
|
|
headerClassName,
|
|
|
|
|
content,
|
|
|
|
|
contentClassName,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
cardClassName?: string;
|
|
|
|
|
header: React.ReactNode;
|
|
|
|
|
headerClassName?: string;
|
|
|
|
|
content: React.ReactNode;
|
|
|
|
|
contentClassName?: string;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<Card
|
|
|
|
|
className={cn("gap-2 border-neutral-200 bg-neutral-200", cardClassName)}
|
|
|
|
|
>
|
|
|
|
|
<CardHeader
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex flex-col items-start justify-between gap-3 space-y-0 px-4 pb-8 sm:flex-row",
|
|
|
|
|
headerClassName,
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{header}
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className={cn("space-y-4 p-4 pt-0", contentClassName)}>
|
|
|
|
|
{content}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-24 15:47:08 +02:00
|
|
|
export const ServizioContent = () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { servizio, isAdmin, userId } = useServizio();
|
|
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
|
2025-08-29 16:18:32 +02:00
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(error.message);
|
|
|
|
|
},
|
2025-08-28 18:27:07 +02:00
|
|
|
onSuccess: async () => {
|
|
|
|
|
toast.success("Servizio modificato con successo");
|
|
|
|
|
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const id = router.asPath.split("#")[1];
|
|
|
|
|
if (id) {
|
|
|
|
|
document.getElementById(id)?.scrollIntoView({
|
|
|
|
|
behavior: "smooth",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
if (servizio.isInterrotto) {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioCard
|
|
|
|
|
cardClassName="bg-neutral-100 border-neutral-300"
|
2025-08-29 16:18:32 +02:00
|
|
|
content={
|
|
|
|
|
<>
|
2025-10-10 16:18:43 +02:00
|
|
|
<p className="font-medium text-lg">Servizio interrotto</p>
|
2025-08-29 16:18:32 +02:00
|
|
|
<p>
|
|
|
|
|
Non è possibile riutilizzare questo servizio, poichè è stato
|
|
|
|
|
interrotto.
|
|
|
|
|
</p>
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-28 18:27:07 +02:00
|
|
|
header={
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
|
2025-10-10 16:18:43 +02:00
|
|
|
<Package className="size-6 text-primary" />
|
2025-08-28 18:27:07 +02:00
|
|
|
Cerco Affitto {servizio.tipologia}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
|
2025-10-10 16:18:43 +02:00
|
|
|
<CardDescription className="space-y-2 pl-2 text-foreground/70">
|
2025-08-28 18:27:07 +02:00
|
|
|
<p>
|
|
|
|
|
aggiunto il{" "}
|
|
|
|
|
{servizio.created_at.toLocaleDateString("it", {
|
|
|
|
|
day: "2-digit",
|
2025-08-29 16:18:32 +02:00
|
|
|
month: "2-digit",
|
|
|
|
|
year: "numeric",
|
2025-08-28 18:27:07 +02:00
|
|
|
})}
|
|
|
|
|
</p>
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!servizio.isOkAcconto || servizio.decorrenza === null) {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioCard
|
|
|
|
|
content={
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
<div className="flex flex-wrap gap-3">
|
|
|
|
|
<Link
|
|
|
|
|
aria-label="Attiva Servizio"
|
2025-08-29 16:18:32 +02:00
|
|
|
href={`/servizio/onboard/${servizio.servizio_id}`}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<Button>Procedi all'attivazione</Button>
|
|
|
|
|
</Link>
|
|
|
|
|
{isAdmin && (
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() =>
|
|
|
|
|
updateServizio({
|
|
|
|
|
data: {
|
|
|
|
|
decorrenza: new Date(),
|
2025-08-29 16:18:32 +02:00
|
|
|
isOkAcconto: true,
|
2025-08-28 18:27:07 +02:00
|
|
|
},
|
2025-08-29 16:18:32 +02:00
|
|
|
servizioId: servizio.servizio_id,
|
2025-08-28 18:27:07 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
variant="success"
|
|
|
|
|
>
|
|
|
|
|
Attiva Manualmente (Azione Admin)
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{isAdmin && (
|
|
|
|
|
<AnnunciSelezionati
|
|
|
|
|
annunci={servizio.annunci}
|
2025-08-29 16:18:32 +02:00
|
|
|
isConfirming={false}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2025-08-29 16:18:32 +02:00
|
|
|
header={
|
|
|
|
|
<div className="flex w-full flex-col gap-2">
|
|
|
|
|
<span className="text-foreground/70 text-lg">
|
|
|
|
|
Servizio non attivo
|
|
|
|
|
</span>
|
|
|
|
|
<ServizioActions2 />
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const annuncioConfermato = servizio.annunci.filter(
|
|
|
|
|
(a) => a.accettato_conferma_at !== null,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (servizio.isOkSaldo) {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioCard
|
2025-08-29 16:18:32 +02:00
|
|
|
content={
|
|
|
|
|
<>
|
|
|
|
|
{annuncioConfermato.map((data) => {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioAnnuncioProvider data={data} key={data.id}>
|
|
|
|
|
<AnnuncioCard />
|
|
|
|
|
</ServizioAnnuncioProvider>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-28 18:27:07 +02:00
|
|
|
header={
|
|
|
|
|
<>
|
|
|
|
|
<ServizioActions2 />
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const openContattiCount = servizio.annunci.filter(
|
|
|
|
|
(a) => a.open_contatti_at !== null,
|
|
|
|
|
).length;
|
|
|
|
|
|
|
|
|
|
const annunciInConferma = servizio.annunci.filter(
|
|
|
|
|
(a) => a.user_confirmed_at !== null,
|
|
|
|
|
);
|
|
|
|
|
const annunciNotInConferma = servizio.annunci.filter(
|
|
|
|
|
(a) => a.user_confirmed_at === null,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<ServizioCard
|
|
|
|
|
content={
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div className="flex items-center gap-2">
|
2025-10-10 16:18:43 +02:00
|
|
|
<UserCircle className="size-6 text-primary" />
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="font-medium">Contatti sbloccati</span>
|
|
|
|
|
</div>
|
|
|
|
|
<span className="text-sm">{openContattiCount} / 10</span>
|
|
|
|
|
</div>
|
2025-08-29 16:18:32 +02:00
|
|
|
<Progress className="h-2" value={openContattiCount * 10} />
|
2025-08-28 18:27:07 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ServizioDuration decorrenza={servizio.decorrenza} />
|
|
|
|
|
|
|
|
|
|
{annuncioConfermato.length > 0 && (
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<div className="flex items-center gap-2">
|
2025-10-10 16:18:43 +02:00
|
|
|
<PackageCheck className="size-6 text-primary" />
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="font-medium">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"
|
2025-08-29 16:18:32 +02:00
|
|
|
key={a.annunci_id}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<h3>
|
|
|
|
|
{a.codice} - confermato il:{" "}
|
|
|
|
|
{/** biome-ignore lint/style/noNonNullAssertion: <already checked above> */}
|
|
|
|
|
{new Date(a.accettato_conferma_at!).toLocaleDateString(
|
|
|
|
|
"it",
|
|
|
|
|
{
|
|
|
|
|
day: "2-digit",
|
|
|
|
|
hour: "2-digit",
|
|
|
|
|
minute: "2-digit",
|
2025-08-29 16:18:32 +02:00
|
|
|
month: "2-digit",
|
|
|
|
|
year: "numeric",
|
2025-08-28 18:27:07 +02:00
|
|
|
},
|
|
|
|
|
)}
|
|
|
|
|
</h3>
|
|
|
|
|
<SaldoButton
|
|
|
|
|
annuncioId={a.annunci_id}
|
2025-08-29 16:18:32 +02:00
|
|
|
className="border-none"
|
|
|
|
|
variant={"outline"}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-10-21 14:35:06 +02:00
|
|
|
{/* <div className="space-y-2">
|
2025-08-28 18:27:07 +02:00
|
|
|
<AnnunciInConferma annunci={annunciInConferma} />
|
|
|
|
|
<AnnunciSelezionati
|
|
|
|
|
annunci={annunciNotInConferma}
|
2025-08-29 16:18:32 +02:00
|
|
|
isConfirming={annunciInConferma.length > 0}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
<AnnunciCompatibili />
|
2025-10-21 14:35:06 +02:00
|
|
|
</div> */}
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<DialogServizio
|
|
|
|
|
title="Annunci in conferma"
|
|
|
|
|
trigger={
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Button
|
|
|
|
|
disabled={annunciInConferma.length === 0}
|
|
|
|
|
size="xl"
|
|
|
|
|
variant="success"
|
|
|
|
|
>
|
|
|
|
|
Annunci in conferma
|
|
|
|
|
</Button>
|
|
|
|
|
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
|
|
|
|
|
{annunciInConferma.length}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
|
|
|
|
|
{annunciInConferma.map((data) => {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioAnnuncioProvider data={data} key={data.id}>
|
|
|
|
|
<AnnuncioCard className="border-2 border-green-500" />
|
|
|
|
|
</ServizioAnnuncioProvider>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</DialogServizio>
|
|
|
|
|
<DialogServizio
|
|
|
|
|
title="Annunci selezionati"
|
|
|
|
|
trigger={
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Button size="xl" variant="orange">
|
|
|
|
|
Annunci selezionati
|
|
|
|
|
</Button>
|
|
|
|
|
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
|
|
|
|
|
{annunciNotInConferma.length}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
|
|
|
|
|
{annunciNotInConferma.length > 0 ? (
|
|
|
|
|
annunciNotInConferma.map((data) => {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioAnnuncioProvider data={data} key={data.id}>
|
|
|
|
|
<AnnuncioCard className="border-2 border-sky-500" />
|
|
|
|
|
</ServizioAnnuncioProvider>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex">
|
|
|
|
|
<span>
|
|
|
|
|
Nessun annuncio selezionato, fai una ricerca nella pagina{" "}
|
|
|
|
|
<Link
|
|
|
|
|
aria-label="Annunci Compatibili"
|
|
|
|
|
className="inline-flex items-center gap-1 text-blue-500 underline"
|
|
|
|
|
href="/annunci"
|
|
|
|
|
>
|
|
|
|
|
Annunci
|
|
|
|
|
<ExternalLink className="size-4" />
|
|
|
|
|
</Link>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</DialogServizio>
|
|
|
|
|
{(() => {
|
|
|
|
|
const { servizioId, userId, isAdmin } = useServizio();
|
|
|
|
|
const { data, isLoading } =
|
|
|
|
|
api.servizio.getCompatibileAnnunci.useQuery({
|
|
|
|
|
servizioId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { data: userIntrests } =
|
|
|
|
|
api.intrests.getUserInterests.useQuery({
|
|
|
|
|
userId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (isLoading) return <div>Loading...</div>;
|
|
|
|
|
return (
|
|
|
|
|
<DialogServizio
|
|
|
|
|
title="Annunci compatibili"
|
|
|
|
|
trigger={
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Button className="bg-purple-500" size="xl">
|
|
|
|
|
Annunci compatibili
|
|
|
|
|
</Button>
|
|
|
|
|
<span className="-translate-y-1/2 absolute top-0 right-0 flex min-w-6 origin-center translate-x-1/2 items-center justify-center rounded-full bg-destructive px-1 text-white">
|
|
|
|
|
{data?.length}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto">
|
|
|
|
|
{!data || data.length === 0 ? (
|
|
|
|
|
<div className="flex items-center justify-center gap-1 rounded-md bg-white p-4 sm:flex">
|
|
|
|
|
<span>
|
|
|
|
|
Nessun annuncio compatibile trovato. Amplia la ricerca
|
|
|
|
|
per trovare annunci compatibili.
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
data.map((a) => {
|
|
|
|
|
return (
|
|
|
|
|
<BasicAnnuncioCard
|
|
|
|
|
className="border-2 border-neutral-500"
|
|
|
|
|
data={a}
|
|
|
|
|
interactions={
|
|
|
|
|
<>
|
|
|
|
|
{isAdmin ? (
|
|
|
|
|
<AddButton annuncioId={a.id} />
|
|
|
|
|
) : (
|
|
|
|
|
<InteressatoButtonBatchV
|
|
|
|
|
annuncioId={a.id}
|
|
|
|
|
hasInterest={
|
|
|
|
|
userIntrests?.includes(a.id) || false
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
key={a.id}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</DialogServizio>
|
|
|
|
|
);
|
|
|
|
|
})()}
|
2025-08-28 18:27:07 +02:00
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-29 16:18:32 +02:00
|
|
|
header={
|
|
|
|
|
<>
|
|
|
|
|
<ServizioActions2 />
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
);
|
2025-08-24 15:47:08 +02:00
|
|
|
};
|
|
|
|
|
|
2025-10-21 14:35:06 +02:00
|
|
|
const DialogServizio = ({
|
|
|
|
|
trigger,
|
|
|
|
|
title,
|
|
|
|
|
children,
|
|
|
|
|
}: {
|
|
|
|
|
trigger: ReactNode;
|
|
|
|
|
title: string;
|
|
|
|
|
children: ReactNode;
|
|
|
|
|
}) => {
|
|
|
|
|
return (
|
|
|
|
|
<Dialog>
|
|
|
|
|
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
|
|
|
|
<DialogContent className="md:max-w-5xl">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>{title}</DialogTitle>
|
|
|
|
|
<DialogDescription className="sr-only">
|
|
|
|
|
Servizio Dialog
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
{children}
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-04 17:45:44 +02:00
|
|
|
const ServizioMain = () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { servizio, isAdmin, userId } = useServizio();
|
|
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
|
2025-08-29 16:18:32 +02:00
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(error.message);
|
|
|
|
|
},
|
2025-08-28 18:27:07 +02:00
|
|
|
onSuccess: async () => {
|
|
|
|
|
toast.success("Servizio modificato con successo");
|
|
|
|
|
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const id = router.asPath.split("#")[1];
|
|
|
|
|
if (id) {
|
|
|
|
|
document.getElementById(id)?.scrollIntoView({
|
|
|
|
|
behavior: "smooth",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
if (servizio.isInterrotto) {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioCard
|
|
|
|
|
cardClassName="bg-neutral-100 border-neutral-300"
|
2025-08-29 16:18:32 +02:00
|
|
|
content={
|
|
|
|
|
<>
|
2025-10-10 16:18:43 +02:00
|
|
|
<p className="font-medium text-lg">Servizio interrotto</p>
|
2025-08-29 16:18:32 +02:00
|
|
|
<p>
|
|
|
|
|
Non è possibile riutilizzare questo servizio, poichè è stato
|
|
|
|
|
interrotto.
|
|
|
|
|
</p>
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-28 18:27:07 +02:00
|
|
|
header={
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
|
2025-10-10 16:18:43 +02:00
|
|
|
<Package className="size-6 text-primary" />
|
2025-08-28 18:27:07 +02:00
|
|
|
Cerco Affitto {servizio.tipologia}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
|
2025-10-10 16:18:43 +02:00
|
|
|
<CardDescription className="space-y-2 pl-2 text-foreground/70">
|
2025-08-28 18:27:07 +02:00
|
|
|
<p>
|
|
|
|
|
aggiunto il{" "}
|
|
|
|
|
{servizio.created_at.toLocaleDateString("it", {
|
|
|
|
|
day: "2-digit",
|
2025-08-29 16:18:32 +02:00
|
|
|
month: "2-digit",
|
|
|
|
|
year: "numeric",
|
2025-08-28 18:27:07 +02:00
|
|
|
})}
|
|
|
|
|
</p>
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!servizio.isOkAcconto || servizio.decorrenza === null) {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioCard
|
|
|
|
|
content={
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
<div className="flex flex-wrap gap-3">
|
|
|
|
|
<Link
|
|
|
|
|
aria-label="Attiva Servizio"
|
2025-08-29 16:18:32 +02:00
|
|
|
href={`/servizio/onboard/${servizio.servizio_id}`}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<Button>Procedi all'attivazione</Button>
|
|
|
|
|
</Link>
|
|
|
|
|
{isAdmin && (
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() =>
|
|
|
|
|
updateServizio({
|
|
|
|
|
data: {
|
|
|
|
|
decorrenza: new Date(),
|
2025-08-29 16:18:32 +02:00
|
|
|
isOkAcconto: true,
|
2025-08-28 18:27:07 +02:00
|
|
|
},
|
2025-08-29 16:18:32 +02:00
|
|
|
servizioId: servizio.servizio_id,
|
2025-08-28 18:27:07 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
variant="success"
|
|
|
|
|
>
|
|
|
|
|
Attiva Manualmente (Azione Admin)
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{isAdmin && (
|
|
|
|
|
<AnnunciSelezionati
|
|
|
|
|
annunci={servizio.annunci}
|
2025-08-29 16:18:32 +02:00
|
|
|
isConfirming={false}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2025-08-29 16:18:32 +02:00
|
|
|
header={
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex w-full flex-col gap-4">
|
|
|
|
|
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
|
2025-10-10 16:18:43 +02:00
|
|
|
<Package className="size-6 text-primary" />
|
2025-08-29 16:18:32 +02:00
|
|
|
Cerco Affitto {servizio.tipologia}
|
|
|
|
|
</CardTitle>
|
2025-10-10 16:18:43 +02:00
|
|
|
<CardDescription className="pl-2 text-foreground/70">
|
2025-08-29 16:18:32 +02:00
|
|
|
<p>Servizio non attivo</p>
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</div>
|
|
|
|
|
<ServizioActions />
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const annuncioConfermato = servizio.annunci.filter(
|
|
|
|
|
(a) => a.accettato_conferma_at !== null,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (servizio.isOkSaldo) {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioCard
|
2025-08-29 16:18:32 +02:00
|
|
|
content={
|
|
|
|
|
<>
|
|
|
|
|
{annuncioConfermato.map((data) => {
|
|
|
|
|
return (
|
|
|
|
|
<ServizioAnnuncioProvider data={data} key={data.id}>
|
|
|
|
|
<AnnuncioCard />
|
|
|
|
|
</ServizioAnnuncioProvider>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-28 18:27:07 +02:00
|
|
|
header={
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex w-full flex-wrap items-center gap-2">
|
|
|
|
|
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
|
2025-10-10 16:18:43 +02:00
|
|
|
<Package className="size-6 text-primary" />
|
2025-08-28 18:27:07 +02:00
|
|
|
Cerco Affitto {servizio.tipologia}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
|
2025-10-10 16:18:43 +02:00
|
|
|
<CardDescription className="space-y-2 pl-2 text-foreground/70">
|
2025-08-28 18:27:07 +02:00
|
|
|
<p>
|
|
|
|
|
aggiunto il{" "}
|
|
|
|
|
{servizio.created_at.toLocaleDateString("it", {
|
|
|
|
|
day: "2-digit",
|
2025-08-29 16:18:32 +02:00
|
|
|
month: "2-digit",
|
|
|
|
|
year: "numeric",
|
2025-08-28 18:27:07 +02:00
|
|
|
})}
|
|
|
|
|
</p>
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ServizioActions />
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const openContattiCount = servizio.annunci.filter(
|
|
|
|
|
(a) => a.open_contatti_at !== null,
|
|
|
|
|
).length;
|
|
|
|
|
|
|
|
|
|
const annunciInConferma = servizio.annunci.filter(
|
|
|
|
|
(a) => a.user_confirmed_at !== null,
|
|
|
|
|
);
|
|
|
|
|
const annunciNotInConferma = servizio.annunci.filter(
|
|
|
|
|
(a) => a.user_confirmed_at === null,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<ServizioCard
|
|
|
|
|
content={
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div className="flex items-center gap-2">
|
2025-10-10 16:18:43 +02:00
|
|
|
<UserCircle className="size-6 text-primary" />
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="font-medium">Contatti sbloccati</span>
|
|
|
|
|
</div>
|
|
|
|
|
<span className="text-sm">{openContattiCount} / 10</span>
|
|
|
|
|
</div>
|
2025-08-29 16:18:32 +02:00
|
|
|
<Progress className="h-2" value={openContattiCount * 10} />
|
2025-08-28 18:27:07 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ServizioDuration decorrenza={servizio.decorrenza} />
|
|
|
|
|
|
|
|
|
|
{annuncioConfermato.length > 0 && (
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<div className="flex items-center gap-2">
|
2025-10-10 16:18:43 +02:00
|
|
|
<PackageCheck className="size-6 text-primary" />
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="font-medium">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"
|
2025-08-29 16:18:32 +02:00
|
|
|
key={a.annunci_id}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<h3>
|
|
|
|
|
{a.codice} - confermato il:{" "}
|
|
|
|
|
{/** biome-ignore lint/style/noNonNullAssertion: <already checked> */}
|
|
|
|
|
{new Date(a.accettato_conferma_at!).toLocaleDateString(
|
|
|
|
|
"it",
|
|
|
|
|
{
|
|
|
|
|
day: "2-digit",
|
|
|
|
|
hour: "2-digit",
|
|
|
|
|
minute: "2-digit",
|
2025-08-29 16:18:32 +02:00
|
|
|
month: "2-digit",
|
|
|
|
|
year: "numeric",
|
2025-08-28 18:27:07 +02:00
|
|
|
},
|
|
|
|
|
)}
|
|
|
|
|
</h3>
|
|
|
|
|
<SaldoButton
|
|
|
|
|
annuncioId={a.annunci_id}
|
2025-08-29 16:18:32 +02:00
|
|
|
className="border-none"
|
|
|
|
|
variant={"outline"}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<AnnunciInConferma annunci={annunciInConferma} />
|
|
|
|
|
<AnnunciSelezionati
|
|
|
|
|
annunci={annunciNotInConferma}
|
2025-08-29 16:18:32 +02:00
|
|
|
isConfirming={annunciInConferma.length > 0}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
<AnnunciCompatibili />
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-29 16:18:32 +02:00
|
|
|
header={
|
|
|
|
|
<>
|
|
|
|
|
<div className="flex w-full flex-col gap-2">
|
|
|
|
|
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
|
2025-10-10 16:18:43 +02:00
|
|
|
<Package className="size-6 text-primary" />
|
2025-08-29 16:18:32 +02:00
|
|
|
Cerco Affitto {servizio.tipologia}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
|
2025-10-10 16:18:43 +02:00
|
|
|
<CardDescription className="space-y-2 pl-2 text-foreground/70">
|
2025-08-29 16:18:32 +02:00
|
|
|
<p>
|
|
|
|
|
aggiunto il{" "}
|
|
|
|
|
{servizio.created_at.toLocaleDateString("it", {
|
|
|
|
|
day: "2-digit",
|
|
|
|
|
month: "2-digit",
|
|
|
|
|
year: "numeric",
|
|
|
|
|
})}
|
|
|
|
|
</p>
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ServizioActions />
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|