2025-08-04 17:45:44 +02:00
|
|
|
import { BedDouble, CalendarClock, Maximize2, Ruler } from "lucide-react";
|
|
|
|
|
import Image from "next/image";
|
2025-08-28 18:27:07 +02:00
|
|
|
import Link from "next/link";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { useState } from "react";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { ImageFlbk } from "~/components/ImageWithFallback";
|
|
|
|
|
import {
|
|
|
|
|
Carousel,
|
|
|
|
|
CarouselContent,
|
|
|
|
|
CarouselItem,
|
|
|
|
|
CarouselNext,
|
|
|
|
|
CarouselPrevious,
|
|
|
|
|
} from "~/components/ui/carousel";
|
2025-08-04 17:45:44 +02:00
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
2025-08-04 17:45:44 +02:00
|
|
|
} from "~/components/ui/dialog";
|
|
|
|
|
import { camereTesti, handleConsegna } from "~/lib/annuncio_details";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { cn, formatCurrency } from "~/lib/utils";
|
|
|
|
|
import { useTranslation } from "~/providers/I18nProvider";
|
2025-09-01 15:59:08 +02:00
|
|
|
import { VideoPlayer } from "./videoPlayer";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
type CardAnnuncioProps = {
|
2025-08-28 18:27:07 +02:00
|
|
|
id: number;
|
|
|
|
|
codice: string;
|
|
|
|
|
prezzo: number;
|
|
|
|
|
titolo: string | null;
|
|
|
|
|
mq: string | null;
|
|
|
|
|
comune: string | null;
|
|
|
|
|
provincia: string | null;
|
|
|
|
|
consegna: number | null;
|
|
|
|
|
camere: number | null;
|
|
|
|
|
immagini: string[] | undefined;
|
|
|
|
|
tipo: string | null;
|
|
|
|
|
stato: string | null;
|
|
|
|
|
className?: string;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-28 10:29:04 +02:00
|
|
|
export const CardAnnuncio = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
id,
|
|
|
|
|
codice,
|
|
|
|
|
prezzo,
|
|
|
|
|
titolo,
|
|
|
|
|
mq,
|
|
|
|
|
comune,
|
|
|
|
|
provincia,
|
|
|
|
|
consegna,
|
|
|
|
|
camere,
|
|
|
|
|
immagini,
|
|
|
|
|
tipo,
|
|
|
|
|
stato,
|
|
|
|
|
className,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: CardAnnuncioProps) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { t } = useTranslation();
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"h-[464px] rounded-xl bg-white shadow-sm shadow-neutral-100 outline outline-neutral-200 hover:shadow-md hover:outline-neutral-300",
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
id={`card-annuncio-${id}`}
|
|
|
|
|
>
|
|
|
|
|
<Link
|
2025-08-29 16:18:32 +02:00
|
|
|
className="block p-2"
|
2025-08-28 18:27:07 +02:00
|
|
|
//target="_blank"
|
2025-08-29 16:18:32 +02:00
|
|
|
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<div className="group relative rounded-xl text-clip">
|
|
|
|
|
{stato === "Trattativa" && (
|
|
|
|
|
<div>
|
|
|
|
|
<div className="absolute z-20 h-56 w-full">
|
|
|
|
|
<div className="absolute bottom-0 w-full touch-none bg-violet-500 text-center text-lg text-white select-none">
|
|
|
|
|
Annuncio in Trattativa
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<Carousel opts={{ loop: true }}>
|
|
|
|
|
<CarouselContent>
|
|
|
|
|
{immagini?.map((img, idx) => (
|
|
|
|
|
<CarouselItem key={`${img}-${idx}`}>
|
|
|
|
|
<ImageFlbk
|
|
|
|
|
alt={t.card.alt_immagine}
|
|
|
|
|
className={
|
|
|
|
|
"h-56 w-full rounded-xl object-cover outline outline-neutral-200"
|
|
|
|
|
}
|
2025-08-29 16:18:32 +02:00
|
|
|
height={400}
|
|
|
|
|
priority={idx === 0}
|
|
|
|
|
src={img}
|
|
|
|
|
width={800}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</CarouselItem>
|
|
|
|
|
))}
|
|
|
|
|
</CarouselContent>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<div
|
2025-08-29 16:18:32 +02:00
|
|
|
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
|
|
|
|
|
onClick={(e) => e.preventDefault()}
|
2025-08-28 18:27:07 +02:00
|
|
|
onKeyDown={(e) => e.stopPropagation()}
|
|
|
|
|
role="button"
|
|
|
|
|
tabIndex={0}
|
|
|
|
|
>
|
|
|
|
|
<CarouselPrevious
|
|
|
|
|
className="left-2 cursor-pointer"
|
|
|
|
|
disabled={!immagini || immagini.length === 1}
|
|
|
|
|
/>
|
|
|
|
|
<CarouselNext
|
|
|
|
|
className="right-2 cursor-pointer"
|
|
|
|
|
disabled={!immagini || immagini.length === 1}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</Carousel>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"mt-2 flex w-full items-center justify-center rounded-md",
|
|
|
|
|
tipo === "Transitorio" && "bg-transitorio",
|
|
|
|
|
tipo === "Stabile" && "bg-stabile",
|
|
|
|
|
tipo === "Cessione" && "bg-blue-400",
|
|
|
|
|
tipo === "Vendita" && "bg-green-400",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<div className="font-semibold text-white">{tipo}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-2 space-y-2">
|
|
|
|
|
<div className="flex flex-row justify-around">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<span className="sr-only">{t.card.codice}</span>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="text-xl font-semibold text-red-600">
|
|
|
|
|
Cod: {codice}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<span className="sr-only">{t.card.prezzo}</span>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="text-xl font-semibold">
|
|
|
|
|
{formatCurrency(prezzo / 1e2)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<div className="text-muted-foreground mb-3 text-center">
|
|
|
|
|
<span className="sr-only">{t.card.titolo}</span>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="line-clamp-2 min-h-[3.5rem] text-base font-medium md:text-lg">
|
|
|
|
|
{titolo}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-muted-foreground text-center">
|
|
|
|
|
<span className="sr-only">{t.card.indirizzo}</span>
|
2025-08-08 17:56:43 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="text-sm font-medium">
|
|
|
|
|
{comune && provincia && `${comune} (${provincia})`}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<div className="text-muted-foreground mt-4 flex items-center justify-around gap-2 text-xs">
|
|
|
|
|
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
|
|
|
|
<Ruler className="size-4 text-indigo-700" />
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<div>
|
|
|
|
|
<p className="font-medium">
|
|
|
|
|
{mq ? Number.parseFloat(mq) : 0}{" "}
|
|
|
|
|
<span>
|
|
|
|
|
m<sup>2</sup>
|
|
|
|
|
</span>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
|
|
|
|
<BedDouble className="size-4 text-indigo-700" />
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<div>
|
|
|
|
|
<p className="font-medium">
|
|
|
|
|
{camereTesti({ camere: camere, testi: t.card })}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
|
|
|
|
<CalendarClock className="size-4 text-indigo-700" />
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<div>
|
|
|
|
|
<p className="truncate font-medium">
|
|
|
|
|
{handleConsegna({
|
2025-08-29 16:18:32 +02:00
|
|
|
aggiornamento: t.card.in_aggiornamento,
|
2025-08-28 18:27:07 +02:00
|
|
|
consegna: consegna,
|
|
|
|
|
consegna_da: t.card.consegna_da,
|
|
|
|
|
mesi: t.preferenze.mesi,
|
|
|
|
|
subito: t.card.consegna_subito,
|
|
|
|
|
})}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const CarouselAnnuncio = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
single,
|
|
|
|
|
immagini,
|
2025-09-01 15:59:08 +02:00
|
|
|
videos,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
single?: boolean;
|
|
|
|
|
immagini: string[] | null;
|
2025-09-01 15:59:08 +02:00
|
|
|
videos: string[] | null;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const [openModal, setOpenModal] = useState(false);
|
|
|
|
|
const [idxModal, setIdxModal] = useState(0);
|
|
|
|
|
function handleOpenModal(position: number) {
|
|
|
|
|
setIdxModal(position);
|
|
|
|
|
setOpenModal(true);
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div className="mx-auto my-4 w-full rounded-md text-clip">
|
|
|
|
|
<div className="relative">
|
2025-08-29 16:18:32 +02:00
|
|
|
<Carousel opts={{ align: "start", loop: true }}>
|
2025-08-28 18:27:07 +02:00
|
|
|
<CarouselContent>
|
|
|
|
|
{immagini?.map((img, idx) => (
|
|
|
|
|
<CarouselItem
|
|
|
|
|
className={cn(
|
|
|
|
|
"group relative",
|
|
|
|
|
immagini && immagini.length === 1
|
|
|
|
|
? ""
|
|
|
|
|
: !single && "md:basis-1/2 lg:basis-1/3",
|
|
|
|
|
)}
|
2025-08-29 16:18:32 +02:00
|
|
|
key={`${img}-${idx}-cF`}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<ImageFlbk
|
|
|
|
|
alt={img}
|
|
|
|
|
className={cn(
|
|
|
|
|
"h-72 w-full rounded-md object-cover",
|
|
|
|
|
immagini.length === 1 && "object-contain",
|
|
|
|
|
)}
|
2025-08-29 16:18:32 +02:00
|
|
|
height={400}
|
|
|
|
|
onClick={() => handleOpenModal(idx)}
|
|
|
|
|
priority={idx === 0}
|
|
|
|
|
src={img}
|
|
|
|
|
width={800}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
<Maximize2
|
2025-09-01 15:59:08 +02:00
|
|
|
className="absolute right-2 bottom-2 transition-all duration-300 hover:scale-150 origin-bottom-right ease-in-out cursor-pointer size-7 rounded-md bg-white/70 stroke-2 p-1 group-hover:opacity-100 sm:opacity-0"
|
2025-08-29 16:18:32 +02:00
|
|
|
onClick={() => handleOpenModal(idx)}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</CarouselItem>
|
|
|
|
|
))}
|
2025-09-01 15:59:08 +02:00
|
|
|
{videos?.map((video, idx) => {
|
|
|
|
|
if (!video) return null;
|
|
|
|
|
if (video.includes("youtu")) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2025-09-01 16:48:03 +02:00
|
|
|
return (
|
|
|
|
|
<CarouselItem
|
|
|
|
|
className={cn(
|
|
|
|
|
"group relative",
|
|
|
|
|
immagini && immagini.length === 1
|
|
|
|
|
? ""
|
|
|
|
|
: !single && "md:basis-1/2 lg:basis-1/3",
|
|
|
|
|
)}
|
|
|
|
|
key={`videoplayer-${video}`}
|
|
|
|
|
>
|
|
|
|
|
<VideoPlayer
|
|
|
|
|
className="h-72 "
|
|
|
|
|
coverImage={immagini?.[0] ? immagini[0] : ""}
|
|
|
|
|
key={`videoplayer-${idx}-${openModal}`}
|
|
|
|
|
videoSrc={video}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Maximize2
|
|
|
|
|
className="absolute right-2 bottom-2 transition-all duration-300 hover:scale-150 origin-bottom-right ease-in-out cursor-pointer size-7 rounded-md bg-white/70 stroke-2 p-1 group-hover:opacity-100 sm:opacity-0"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
handleOpenModal(immagini?.length || 0 + idx)
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</CarouselItem>
|
|
|
|
|
);
|
2025-09-01 15:59:08 +02:00
|
|
|
})}
|
2025-08-28 18:27:07 +02:00
|
|
|
</CarouselContent>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<CarouselPrevious
|
|
|
|
|
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
|
|
|
|
|
disabled={!immagini || immagini.length === 1}
|
2025-08-29 16:18:32 +02:00
|
|
|
type="button"
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
<CarouselNext
|
|
|
|
|
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
|
|
|
|
|
disabled={!immagini || immagini.length === 1}
|
2025-08-29 16:18:32 +02:00
|
|
|
type="button"
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</Carousel>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-29 16:18:32 +02:00
|
|
|
<Dialog onOpenChange={setOpenModal} open={openModal}>
|
2025-08-28 18:27:07 +02:00
|
|
|
<DialogContent className="flex h-full w-full max-w-full items-center justify-center border-none border-transparent bg-white p-0 md:max-w-[80vw] md:p-6 [&_#dialog-close>svg]:size-8">
|
|
|
|
|
<DialogHeader className="absolute top-0 right-0 left-0 z-10">
|
|
|
|
|
<DialogTitle className="sr-only">Image Dialog</DialogTitle>
|
|
|
|
|
<DialogDescription className="sr-only">Immagine</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
<Carousel opts={{ loop: true, startIndex: idxModal }}>
|
|
|
|
|
<CarouselContent>
|
|
|
|
|
{immagini?.map((img, idx) => (
|
|
|
|
|
<CarouselItem key={`${img}-${idx}-cD`}>
|
|
|
|
|
<Image
|
|
|
|
|
alt={`carousel-img-${idx}`}
|
2025-09-01 15:59:08 +02:00
|
|
|
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw] "
|
2025-08-28 18:27:07 +02:00
|
|
|
height={1080}
|
|
|
|
|
src={img}
|
2025-08-29 16:18:32 +02:00
|
|
|
width={1920}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</CarouselItem>
|
|
|
|
|
))}
|
2025-09-01 15:59:08 +02:00
|
|
|
{videos?.map((video) => {
|
|
|
|
|
if (!video) return null;
|
|
|
|
|
if (video.includes("youtu")) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2025-09-01 16:48:03 +02:00
|
|
|
return (
|
|
|
|
|
<CarouselItem
|
|
|
|
|
className="relative flex items-center justify-center"
|
|
|
|
|
key={`carousel-videoplayer-${video}`}
|
|
|
|
|
>
|
|
|
|
|
<VideoPlayer
|
|
|
|
|
className="h-[90vh] absolute w-full mx-auto max-w-fit rounded-md object-contain"
|
|
|
|
|
coverImage={immagini?.[0] ? immagini[0] : ""}
|
|
|
|
|
videoSrc={video}
|
|
|
|
|
/>
|
|
|
|
|
</CarouselItem>
|
|
|
|
|
);
|
2025-09-01 15:59:08 +02:00
|
|
|
})}
|
2025-08-28 18:27:07 +02:00
|
|
|
</CarouselContent>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<CarouselPrevious
|
|
|
|
|
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
|
|
|
|
|
disabled={!immagini || immagini.length === 1}
|
2025-08-29 16:18:32 +02:00
|
|
|
type="button"
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
<CarouselNext
|
|
|
|
|
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
|
|
|
|
|
disabled={!immagini || immagini.length === 1}
|
2025-08-29 16:18:32 +02:00
|
|
|
type="button"
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</Carousel>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|