2025-11-07 17:03:08 +01:00
|
|
|
import { ExternalLink, UnfoldVertical } from "lucide-react";
|
|
|
|
|
import Link from "next/link";
|
2025-11-11 15:41:07 +01:00
|
|
|
import { useState } from "react";
|
2025-11-07 17:03:08 +01:00
|
|
|
import { replaceWithBr } from "~/lib/newlineToBr";
|
|
|
|
|
import { cn, formatCurrency } from "~/lib/utils";
|
|
|
|
|
import { useTranslation } from "~/providers/I18nProvider";
|
|
|
|
|
import { useServizio } from "~/providers/ServizioProvider";
|
|
|
|
|
import type { Annunci } from "~/schemas/public/Annunci";
|
|
|
|
|
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
|
|
|
|
import { api } from "~/utils/api";
|
|
|
|
|
import { CardAnnuncio, CarouselAnnuncio } from "../annuncio_card";
|
|
|
|
|
import { InteressatoButtonServizio } from "../annuncio-interactions/annuncio_interactions";
|
|
|
|
|
import { LoadingPage } from "../loading";
|
|
|
|
|
import { Badge } from "../ui/badge";
|
|
|
|
|
import { Button } from "../ui/button";
|
|
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogClose,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogFooter,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogTrigger,
|
|
|
|
|
} from "../ui/dialog";
|
2025-11-11 15:41:07 +01:00
|
|
|
import { Label } from "../ui/label";
|
|
|
|
|
import { Switch } from "../ui/switch";
|
2025-11-07 17:03:08 +01:00
|
|
|
|
|
|
|
|
export const AnnunciCompatibili = () => {
|
2025-11-11 15:41:07 +01:00
|
|
|
const { servizioId, isAdmin } = useServizio();
|
|
|
|
|
const [adminOverride, setAdminOverride] = useState(false);
|
2025-11-07 17:03:08 +01:00
|
|
|
const { data, isLoading } = api.servizio.getCompatibileAnnunci.useQuery({
|
|
|
|
|
servizioId,
|
2025-11-11 15:41:07 +01:00
|
|
|
adminOverride,
|
2025-11-07 17:03:08 +01:00
|
|
|
});
|
|
|
|
|
return (
|
|
|
|
|
<Dialog>
|
|
|
|
|
<DialogTrigger asChild>
|
|
|
|
|
<Button className="w-full bg-purple-500 sm:w-fit">
|
|
|
|
|
Esplora Annunci Compatibili
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogTrigger>
|
2025-11-11 15:41:07 +01:00
|
|
|
<DialogContent className="max-w-xl p-2 sm:max-w-5xl sm:p-6 md:max-w-7xl 2xl:max-w-[100rem]">
|
|
|
|
|
<DialogHeader className="flex-row flex-wrap items-center gap-5 pt-6">
|
|
|
|
|
<DialogTitle>
|
2025-11-07 17:03:08 +01:00
|
|
|
Annunci compatibili con le preferenze del servizio
|
|
|
|
|
</DialogTitle>
|
2025-11-11 15:41:07 +01:00
|
|
|
{isAdmin && (
|
|
|
|
|
<div className="flex flex-wrap items-center gap-x-2">
|
|
|
|
|
<Label htmlFor="adminOverride">
|
|
|
|
|
Ignora limitazioni preferenze
|
|
|
|
|
</Label>
|
|
|
|
|
|
|
|
|
|
<Switch
|
|
|
|
|
checked={adminOverride}
|
|
|
|
|
className="data-[state=checked]:bg-neutral-700"
|
|
|
|
|
id="adminOverride"
|
|
|
|
|
onCheckedChange={setAdminOverride}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-11-07 17:03:08 +01:00
|
|
|
<DialogDescription className="sr-only"></DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
<div className="flex max-h-[80vh] flex-col gap-2 overflow-y-auto p-0.5 sm:p-2">
|
|
|
|
|
{isLoading ? (
|
|
|
|
|
<LoadingPage />
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
{!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>
|
|
|
|
|
) : (
|
2025-11-11 15:41:07 +01:00
|
|
|
<div className="relative z-0 mx-auto grid max-w-full grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 sm:gap-y-10 lg:grid-cols-3 2xl:grid-cols-4">
|
2025-11-07 17:03:08 +01:00
|
|
|
{data.map((annuncio) => (
|
|
|
|
|
<div
|
|
|
|
|
className="flex flex-col justify-center gap-1"
|
|
|
|
|
key={annuncio.codice}
|
|
|
|
|
>
|
|
|
|
|
<CardAnnuncio
|
|
|
|
|
{...annuncio}
|
|
|
|
|
className="outline outline-neutral-500"
|
|
|
|
|
noLink
|
|
|
|
|
onlyFirstImage
|
|
|
|
|
/>
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
<div className="w-full">
|
|
|
|
|
<AnnuncioDettaglio data={annuncio} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="w-full">
|
|
|
|
|
<InteressatoButtonServizio annuncioId={annuncio.id} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
<DialogClose asChild></DialogClose>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
|
|
|
|
|
return (
|
|
|
|
|
<Badge
|
|
|
|
|
className={cn(
|
|
|
|
|
"font-semibold text-md",
|
|
|
|
|
tipo === "Transitorio" && "bg-transitorio",
|
|
|
|
|
tipo === "Stabile" && "bg-stabile",
|
|
|
|
|
tipo === "Cessione" && "bg-blue-400",
|
|
|
|
|
tipo === "Vendita" && "bg-green-400",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
Affitto {tipo}
|
|
|
|
|
</Badge>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AnnuncioDettaglio = ({ data }: { data: AnnuncioRicerca }) => {
|
|
|
|
|
const { locale, t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Dialog>
|
|
|
|
|
<DialogTrigger asChild>
|
|
|
|
|
<Button aria-label="Dettagli Annuncio" variant="outline">
|
|
|
|
|
<UnfoldVertical className="size-6" /> Dettagli annuncio
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogTrigger>
|
|
|
|
|
<DialogContent className="max-h-[70vh] max-w-xl overflow-y-auto sm:max-w-6xl">
|
|
|
|
|
<DialogHeader className="flex-row items-center gap-4">
|
|
|
|
|
<DialogTitle className="text-xl">Annuncio {data.codice}</DialogTitle>
|
|
|
|
|
<Link href={`/annuncio/${data.codice}`} target="_blank">
|
|
|
|
|
<Button size="sm" variant="info">
|
|
|
|
|
<span>Vai alla pagina completa</span>{" "}
|
|
|
|
|
<ExternalLink className="size-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</Link>
|
|
|
|
|
<DialogDescription className="sr-only">
|
|
|
|
|
Annuncio {data.codice}
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
<div className="flex flex-col gap-2 px-2">
|
|
|
|
|
<div className="w-full contain-inline-size">
|
|
|
|
|
<CarouselAnnuncio
|
|
|
|
|
immagini={data.images.map((img) => img.img)}
|
|
|
|
|
updated_at={data.media_updated_at}
|
|
|
|
|
videos={data.url_video}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<TipoBadge tipo={data.tipo} />
|
|
|
|
|
{data.stato === "Trattativa" && (
|
|
|
|
|
<Badge className="bg-violet-500 font-semibold text-md">
|
|
|
|
|
Annuncio in Trattativa
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mt-1 space-y-2">
|
|
|
|
|
<div className="flex flex-row justify-around">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<dt className="sr-only">{t.card.codice}</dt>
|
|
|
|
|
|
|
|
|
|
<dd className="font-semibold text-lg">Cod: {data.codice}</dd>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<dt className="sr-only">{t.card.prezzo}</dt>
|
|
|
|
|
|
|
|
|
|
<dd className="font-semibold text-lg">
|
|
|
|
|
{formatCurrency(data.prezzo / 1e2)}
|
|
|
|
|
</dd>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mb-3">
|
|
|
|
|
<dt className="sr-only">{t.card.titolo}</dt>
|
|
|
|
|
|
|
|
|
|
<dd className="font-semibold text-base md:text-lg">
|
|
|
|
|
{locale === "it" ? data.titolo_it : data.titolo_en}
|
|
|
|
|
</dd>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mb-3">
|
|
|
|
|
<dd
|
|
|
|
|
className="text-base"
|
|
|
|
|
dangerouslySetInnerHTML={{
|
|
|
|
|
__html: replaceWithBr(
|
|
|
|
|
(locale === "it" ? data.desc_it : data.desc_en) || "",
|
|
|
|
|
),
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
<DialogClose asChild></DialogClose>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
};
|