2025-11-03 18:49:27 +01:00
|
|
|
import {
|
|
|
|
|
BedDouble,
|
|
|
|
|
CalendarClock,
|
|
|
|
|
MapPin,
|
|
|
|
|
Maximize2,
|
|
|
|
|
Ruler,
|
|
|
|
|
Siren,
|
|
|
|
|
Star,
|
|
|
|
|
TrafficCone,
|
|
|
|
|
} from "lucide-react";
|
2025-08-28 18:27:07 +02:00
|
|
|
import Link from "next/link";
|
2025-11-12 15:52:04 +01:00
|
|
|
import { type RefObject, useEffect, useRef, useState } from "react";
|
|
|
|
|
import { useIntersection } from "react-use";
|
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-11-18 15:20:14 +01:00
|
|
|
import type { ImagesRefs } from "~/schemas/public/ImagesRefs";
|
|
|
|
|
import type { VideosRefs } from "~/schemas/public/VideosRefs";
|
2025-11-03 18:49:27 +01:00
|
|
|
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
|
|
|
|
import { Badge } from "./ui/badge";
|
2025-11-12 15:52:04 +01:00
|
|
|
import { Skeleton } from "./ui/skeleton";
|
2025-09-01 15:59:08 +02:00
|
|
|
import { VideoPlayer } from "./videoPlayer";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-03 18:49:27 +01:00
|
|
|
type CardAnnuncioProps = AnnuncioRicerca & {
|
2025-08-28 18:27:07 +02:00
|
|
|
className?: string;
|
2025-11-04 19:18:13 +01:00
|
|
|
noLink?: boolean;
|
|
|
|
|
onlyFirstImage?: boolean;
|
2025-11-12 15:52:04 +01:00
|
|
|
eager?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const LazyCardAnnuncio = (props: CardAnnuncioProps) => {
|
|
|
|
|
const { eager = false } = props;
|
|
|
|
|
const intersectionRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const hasRenderedRef = useRef(eager);
|
|
|
|
|
const [shouldRender, setShouldRender] = useState(eager);
|
|
|
|
|
|
|
|
|
|
const intersection = useIntersection(
|
|
|
|
|
intersectionRef as RefObject<HTMLElement>,
|
|
|
|
|
{
|
|
|
|
|
root: null,
|
|
|
|
|
rootMargin: "300px",
|
|
|
|
|
threshold: 0.01,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (
|
|
|
|
|
!hasRenderedRef.current &&
|
|
|
|
|
intersection &&
|
|
|
|
|
intersection.intersectionRatio > 0
|
|
|
|
|
) {
|
|
|
|
|
hasRenderedRef.current = true;
|
|
|
|
|
setShouldRender(true);
|
|
|
|
|
}
|
|
|
|
|
}, [intersection]);
|
|
|
|
|
|
|
|
|
|
return (
|
2025-11-13 15:39:44 +01:00
|
|
|
<div
|
|
|
|
|
className="min-h-[31.1rem]"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
}}
|
|
|
|
|
onKeyDown={(e) => e.stopPropagation()}
|
|
|
|
|
ref={intersectionRef}
|
|
|
|
|
role="button"
|
|
|
|
|
tabIndex={0}
|
|
|
|
|
>
|
2025-11-12 15:52:04 +01:00
|
|
|
{shouldRender ? <CardAnnuncio {...props} /> : <SkeletonCardAnnuncio />}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
2025-11-12 15:52:04 +01:00
|
|
|
const SkeletonCardAnnuncio = () => {
|
|
|
|
|
return (
|
2025-11-14 17:21:21 +01:00
|
|
|
<div className="h-[31.1rem] rounded-md bg-secondary p-2 outline outline-neutral-300 hover:outline-neutral-600">
|
2025-11-12 15:52:04 +01:00
|
|
|
<div className="flex h-full flex-col gap-2 p-2">
|
|
|
|
|
<Skeleton className="h-64 w-full rounded-t-md" />
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<Skeleton className="h-8 w-full rounded-md" />
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-row justify-between gap-3 px-4">
|
|
|
|
|
<Skeleton className="h-10 w-full rounded-md" />
|
|
|
|
|
<Skeleton className="h-10 w-full rounded-md" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex h-[8.5rem] flex-col justify-between">
|
|
|
|
|
<Skeleton className="h-20 w-full rounded-md" />
|
|
|
|
|
|
|
|
|
|
<div className="flex h-9 items-center justify-around gap-1.5">
|
|
|
|
|
<Skeleton className="size-full rounded-md" />
|
|
|
|
|
<Skeleton className="size-full rounded-md" />
|
|
|
|
|
<Skeleton className="size-full rounded-md" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
2025-08-28 10:29:04 +02:00
|
|
|
export const CardAnnuncio = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
id,
|
|
|
|
|
codice,
|
|
|
|
|
prezzo,
|
2025-11-03 18:49:27 +01:00
|
|
|
titolo_it,
|
|
|
|
|
titolo_en,
|
2025-08-28 18:27:07 +02:00
|
|
|
mq,
|
|
|
|
|
comune,
|
|
|
|
|
provincia,
|
|
|
|
|
consegna,
|
2025-11-03 18:49:27 +01:00
|
|
|
numero_camere: camere,
|
2025-08-28 18:27:07 +02:00
|
|
|
tipo,
|
|
|
|
|
stato,
|
|
|
|
|
className,
|
2025-11-18 15:20:14 +01:00
|
|
|
videos,
|
2025-11-06 13:17:20 +01:00
|
|
|
media_updated_at,
|
2025-10-27 17:58:42 +01:00
|
|
|
images,
|
2025-11-03 18:49:27 +01:00
|
|
|
homepage,
|
2025-11-04 19:18:13 +01:00
|
|
|
noLink = false,
|
|
|
|
|
onlyFirstImage = false,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: CardAnnuncioProps) => {
|
2025-11-03 18:49:27 +01:00
|
|
|
const { t, locale } = useTranslation();
|
|
|
|
|
const currentMonth = new Date().getMonth() + 1;
|
|
|
|
|
const isAvailableNow = consegna === 0 || consegna === currentMonth;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-04 19:18:13 +01:00
|
|
|
const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
|
|
|
|
if (noLink) {
|
|
|
|
|
return <div className="flex h-full flex-col gap-2 p-2">{children}</div>;
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
className="flex h-full flex-col gap-2 p-2"
|
|
|
|
|
href={`/annuncio/${codice}`}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
2025-11-17 19:21:40 +01:00
|
|
|
"relative h-[31.1rem] rounded-md bg-secondary outline outline-primary/60 hover:outline-primary dark:bg-primary dark:outline-primary",
|
2025-08-28 18:27:07 +02:00
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
id={`card-annuncio-${id}`}
|
|
|
|
|
>
|
2025-11-04 19:18:13 +01:00
|
|
|
<Wrapper>
|
2025-11-03 18:49:27 +01:00
|
|
|
<div
|
|
|
|
|
className={cn("group relative overflow-clip text-clip rounded-md")}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
2025-11-13 15:39:44 +01:00
|
|
|
"pointer-events-none absolute inset-0 z-20 size-full rounded-md",
|
2025-11-03 18:49:27 +01:00
|
|
|
homepage &&
|
|
|
|
|
(isAvailableNow
|
2025-11-14 17:21:21 +01:00
|
|
|
? "border-[.35rem] border-dasubito"
|
|
|
|
|
: "border-[.35rem] border-evidenza"),
|
2025-11-03 18:49:27 +01:00
|
|
|
|
2025-11-14 17:21:21 +01:00
|
|
|
stato === "Trattativa" && "border-[.35rem] border-trattativa",
|
2025-11-03 18:49:27 +01:00
|
|
|
)}
|
|
|
|
|
></div>
|
|
|
|
|
{stato !== "Trattativa" && homepage && (
|
|
|
|
|
<div className="absolute top-1 z-20 flex w-full justify-center px-2">
|
|
|
|
|
<Badge
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-1.5 rounded-t-none px-1 py-0.5 font-semibold text-base text-white shadow-lg [&>svg]:size-4.5",
|
2025-11-14 17:21:21 +01:00
|
|
|
isAvailableNow
|
|
|
|
|
? "bg-dasubito"
|
|
|
|
|
: "bg-evidenza text-primary dark:text-accent",
|
2025-11-03 18:49:27 +01:00
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{isAvailableNow ? (
|
|
|
|
|
<>
|
2025-11-14 17:21:21 +01:00
|
|
|
<span>Libero Subito</span>
|
2025-11-03 18:49:27 +01:00
|
|
|
<Siren />
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<span>In Evidenza</span>
|
|
|
|
|
<Star />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-08-28 18:27:07 +02:00
|
|
|
{stato === "Trattativa" && (
|
2025-11-03 18:49:27 +01:00
|
|
|
<div className="absolute top-1 z-20 flex w-full justify-center px-2">
|
|
|
|
|
<Badge
|
|
|
|
|
className={cn(
|
2025-11-14 17:21:21 +01:00
|
|
|
"flex items-center gap-1.5 rounded-t-none bg-trattativa px-1 py-0.5 font-semibold text-base text-white shadow-lg [&>svg]:size-4.5",
|
2025-11-03 18:49:27 +01:00
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<span>In Trattativa</span>
|
|
|
|
|
<TrafficCone />
|
|
|
|
|
</Badge>
|
2025-08-28 18:27:07 +02:00
|
|
|
</div>
|
|
|
|
|
)}
|
2025-11-04 19:18:13 +01:00
|
|
|
{!onlyFirstImage ? (
|
|
|
|
|
<Carousel opts={{ loop: true }}>
|
|
|
|
|
<CarouselContent>
|
|
|
|
|
{images?.map((img, idx) => (
|
|
|
|
|
<CarouselItem key={`${img.img}key`}>
|
|
|
|
|
<ImageFlbk
|
|
|
|
|
alt={t.card.alt_immagine}
|
2025-11-24 18:03:13 +01:00
|
|
|
blurDataURL={`/storage-api/get/${img.thumb}?image=true&cachekey=${media_updated_at?.toString()}`}
|
2025-11-18 15:48:11 +01:00
|
|
|
className={cn(
|
|
|
|
|
"h-64 w-full bg-[#e6e9ec] object-cover",
|
|
|
|
|
(stato === "Trattativa" || homepage) && "rounded-[7px]",
|
|
|
|
|
)}
|
2025-11-04 19:18:13 +01:00
|
|
|
height={1080}
|
|
|
|
|
priority={idx === 0}
|
2025-11-24 18:03:13 +01:00
|
|
|
src={`/storage-api/get/${img.img}?image=true&cachekey=${media_updated_at?.toString()}`}
|
2025-11-04 19:18:13 +01:00
|
|
|
width={1920}
|
2025-09-01 17:31:19 +02:00
|
|
|
/>
|
|
|
|
|
</CarouselItem>
|
2025-11-04 19:18:13 +01:00
|
|
|
))}
|
|
|
|
|
{videos?.map((video) => {
|
|
|
|
|
return (
|
|
|
|
|
<CarouselItem
|
|
|
|
|
className={cn("group relative")}
|
|
|
|
|
key={`videoplayer-${video}`}
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<VideoPlayer
|
2025-11-18 15:20:14 +01:00
|
|
|
className="h-64 object-cover"
|
2025-11-24 18:03:13 +01:00
|
|
|
coverImage={`/storage-api/get/${video.thumb}?image=true&cachekey=${media_updated_at?.toString()}`}
|
2025-11-04 19:18:13 +01:00
|
|
|
key={`videoplayer-${video}`}
|
2025-11-24 18:03:13 +01:00
|
|
|
videoSrc={`/storage-api/get/${video.video}?video=true&cachekey=${media_updated_at?.toString()}`}
|
2025-11-04 19:18:13 +01:00
|
|
|
/>
|
|
|
|
|
</CarouselItem>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</CarouselContent>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-04 19:18:13 +01:00
|
|
|
<div
|
|
|
|
|
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
}}
|
|
|
|
|
onKeyDown={(e) => e.stopPropagation()}
|
|
|
|
|
role="button"
|
|
|
|
|
tabIndex={0}
|
|
|
|
|
>
|
|
|
|
|
<CarouselPrevious
|
|
|
|
|
className="left-2 cursor-pointer"
|
|
|
|
|
disabled={!images || images.length === 1}
|
|
|
|
|
/>
|
|
|
|
|
<CarouselNext
|
|
|
|
|
className="right-2 cursor-pointer"
|
|
|
|
|
disabled={!images || images.length === 1}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</Carousel>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
{images && images.length > 0 && images[0] ? (
|
|
|
|
|
<ImageFlbk
|
|
|
|
|
alt={t.card.alt_immagine}
|
2025-11-24 18:03:13 +01:00
|
|
|
blurDataURL={`/storage-api/get/${images[0].thumb}?image=true&cachekey=${media_updated_at?.toString()}`}
|
2025-11-17 18:35:30 +01:00
|
|
|
className={"h-64 w-full bg-[#e6e9ec] object-cover"}
|
2025-11-04 19:18:13 +01:00
|
|
|
height={1080}
|
|
|
|
|
priority={true}
|
2025-11-24 18:03:13 +01:00
|
|
|
src={`/storage-api/get/${images[0].img}?image=true&cachekey=${media_updated_at?.toString()}`}
|
2025-11-04 19:18:13 +01:00
|
|
|
width={1920}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex h-64 w-full items-center justify-center bg-neutral-200 text-neutral-500"></div>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2025-08-28 18:27:07 +02:00
|
|
|
</div>
|
2025-11-03 18:49:27 +01:00
|
|
|
<div className="flex h-full grow flex-col gap-2">
|
2025-11-07 17:03:08 +01:00
|
|
|
<Badge
|
2025-11-03 18:49:27 +01:00
|
|
|
className={cn(
|
2025-11-17 19:21:40 +01:00
|
|
|
"w-full font-semibold text-md text-white",
|
2025-11-03 18:49:27 +01:00
|
|
|
tipo === "Transitorio" && "bg-transitorio",
|
|
|
|
|
tipo === "Stabile" && "bg-stabile",
|
|
|
|
|
tipo === "Cessione" && "bg-blue-400",
|
|
|
|
|
tipo === "Vendita" && "bg-green-400",
|
|
|
|
|
)}
|
|
|
|
|
>
|
2025-11-07 17:03:08 +01:00
|
|
|
Affitto {tipo}
|
|
|
|
|
</Badge>
|
2025-11-03 18:49:27 +01:00
|
|
|
<div className="flex flex-row justify-between gap-3 px-4">
|
|
|
|
|
<div className="w-full text-center">
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="sr-only">{t.card.codice}</span>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-14 17:21:21 +01:00
|
|
|
<span className="font-semibold text-destructive text-xl">
|
2025-08-28 18:27:07 +02:00
|
|
|
Cod: {codice}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
2025-11-14 17:21:21 +01:00
|
|
|
<div className="w-full text-center text-primary dark:text-accent">
|
2025-08-28 18:27:07 +02:00
|
|
|
<span className="sr-only">{t.card.prezzo}</span>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-10-10 16:18:43 +02:00
|
|
|
<span className="font-semibold text-xl">
|
2025-08-28 18:27:07 +02:00
|
|
|
{formatCurrency(prezzo / 1e2)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-11-03 18:49:27 +01:00
|
|
|
<div className="flex h-full grow flex-col justify-between">
|
2025-11-14 17:21:21 +01:00
|
|
|
<div className="px-4 text-center text-primary dark:text-accent">
|
2025-11-03 18:49:27 +01:00
|
|
|
<span className="sr-only">{t.card.titolo}</span>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-03 18:49:27 +01:00
|
|
|
<span className="line-clamp-3 overflow-ellipsis font-medium text-base">
|
|
|
|
|
{locale === "it" ? titolo_it : titolo_en}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-14 17:21:21 +01:00
|
|
|
<div className="text-center text-primary dark:text-accent">
|
|
|
|
|
<span className="sr-only">{t.card.indirizzo}</span>
|
|
|
|
|
<MapPin className="mr-1 mb-1 inline-block size-4" />
|
|
|
|
|
<span className="font-medium text-sm">
|
|
|
|
|
{comune && provincia && `${comune} (${provincia})`}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex h-9 items-center justify-around gap-1.5 text-primary text-xs">
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-primary-foreground">
|
|
|
|
|
<Ruler className="size-4" />
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-14 17:21:21 +01:00
|
|
|
<div>
|
|
|
|
|
<p className="font-medium">
|
|
|
|
|
{mq ? Number.parseFloat(mq) : 0}{" "}
|
|
|
|
|
<span>
|
|
|
|
|
m<sup>2</sup>
|
|
|
|
|
</span>
|
|
|
|
|
</p>
|
2025-11-03 18:49:27 +01:00
|
|
|
</div>
|
2025-11-14 17:21:21 +01:00
|
|
|
</div>
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-primary-foreground">
|
|
|
|
|
<BedDouble className="size-4" />
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-14 17:21:21 +01:00
|
|
|
<div>
|
|
|
|
|
<p className="font-medium">
|
|
|
|
|
{camereTesti({ camere: camere, testi: t.card })}
|
|
|
|
|
</p>
|
2025-11-03 18:49:27 +01:00
|
|
|
</div>
|
2025-11-14 17:21:21 +01:00
|
|
|
</div>
|
|
|
|
|
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-primary-foreground">
|
|
|
|
|
<CalendarClock className="size-4" />
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-11-14 17:21:21 +01:00
|
|
|
<div>
|
|
|
|
|
<p className="truncate font-medium">
|
|
|
|
|
{handleConsegna({
|
|
|
|
|
aggiornamento: t.card.in_aggiornamento,
|
|
|
|
|
consegna: consegna,
|
|
|
|
|
consegna_da: t.card.consegna_da,
|
|
|
|
|
mesi: t.preferenze.mesi,
|
|
|
|
|
subito: t.card.consegna_subito,
|
|
|
|
|
})}
|
|
|
|
|
</p>
|
2025-11-03 18:49:27 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-28 18:27:07 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-11-04 19:18:13 +01:00
|
|
|
</Wrapper>
|
2025-08-28 18:27:07 +02:00
|
|
|
</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-10-21 18:42:03 +02:00
|
|
|
updated_at,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
single?: boolean;
|
2025-11-18 15:20:14 +01:00
|
|
|
immagini: Pick<ImagesRefs, "img" | "thumb">[];
|
|
|
|
|
videos: Pick<VideosRefs, "video" | "thumb">[];
|
2025-10-21 18:42:03 +02:00
|
|
|
updated_at: Date | 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 (
|
|
|
|
|
<>
|
2025-10-10 16:18:43 +02:00
|
|
|
<div className="mx-auto my-4 w-full text-clip rounded-md">
|
2025-08-28 18:27:07 +02:00
|
|
|
<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(
|
2025-10-20 11:35:48 +02:00
|
|
|
"group relative flex items-center justify-center",
|
2025-08-28 18:27:07 +02:00
|
|
|
immagini && immagini.length === 1
|
|
|
|
|
? ""
|
|
|
|
|
: !single && "md:basis-1/2 lg:basis-1/3",
|
|
|
|
|
)}
|
2025-11-18 15:20:14 +01:00
|
|
|
key={`${img.img}-cF`}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
<ImageFlbk
|
2025-11-18 15:20:14 +01:00
|
|
|
alt={img.img}
|
2025-08-28 18:27:07 +02:00
|
|
|
className={cn(
|
2025-11-14 17:21:21 +01:00
|
|
|
"aspect-square max-h-92 w-full rounded-md bg-[#e6e9ec] object-cover sm:max-h-80 xl:max-h-[25rem]",
|
2025-08-28 18:27:07 +02:00
|
|
|
immagini.length === 1 && "object-contain",
|
|
|
|
|
)}
|
2025-08-29 16:18:32 +02:00
|
|
|
height={400}
|
|
|
|
|
onClick={() => handleOpenModal(idx)}
|
|
|
|
|
priority={idx === 0}
|
2025-11-24 18:03:13 +01:00
|
|
|
src={`/storage-api/get/${img.img}?image=true&cachekey=${updated_at?.toString()}`}
|
2025-08-29 16:18:32 +02:00
|
|
|
width={800}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
<Maximize2
|
2025-11-14 17:21:21 +01:00
|
|
|
className="absolute right-2 bottom-2 size-7 origin-bottom-right cursor-pointer rounded-md bg-muted-foreground/60 stroke-2 stroke-muted p-1 transition-all duration-300 ease-in-out hover:scale-150 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) => {
|
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",
|
|
|
|
|
)}
|
2025-11-18 15:20:14 +01:00
|
|
|
key={`videoplayer-${video.video}`}
|
2025-09-01 16:48:03 +02:00
|
|
|
>
|
|
|
|
|
<VideoPlayer
|
2025-11-18 15:20:14 +01:00
|
|
|
className="aspect-square max-h-92 object-cover sm:max-h-80 xl:max-h-[25rem]"
|
2025-11-24 18:03:13 +01:00
|
|
|
coverImage={`/storage-api/get/${video.thumb}?image=true&cachekey=${updated_at?.toString()}`}
|
2025-09-01 16:48:03 +02:00
|
|
|
key={`videoplayer-${idx}-${openModal}`}
|
2025-11-24 18:03:13 +01:00
|
|
|
videoSrc={`/storage-api/get/${video.video}?video=true&cachekey=${updated_at?.toString()}`}
|
2025-09-01 16:48:03 +02:00
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Maximize2
|
2025-11-14 17:21:21 +01:00
|
|
|
className="absolute right-2 bottom-2 size-7 origin-bottom-right cursor-pointer rounded-md bg-muted-foreground/60 stroke-2 stroke-muted p-1 transition-all duration-300 ease-in-out hover:scale-150 group-hover:opacity-100 sm:opacity-0"
|
2025-09-01 16:48:03 +02:00
|
|
|
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
|
2025-11-14 17:21:21 +01:00
|
|
|
className="left-2 cursor-pointer opacity-60 disabled:opacity-0 dark:border-muted-foreground dark:bg-muted-foreground dark:text-muted hover:dark:bg-muted-foreground/80"
|
2025-08-28 18:27:07 +02:00
|
|
|
disabled={!immagini || immagini.length === 1}
|
2025-08-29 16:18:32 +02:00
|
|
|
type="button"
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
<CarouselNext
|
2025-11-14 17:21:21 +01:00
|
|
|
className="right-2 cursor-pointer opacity-60 disabled:opacity-0 dark:border-muted-foreground dark:bg-muted-foreground dark:text-muted hover:dark:bg-muted-foreground/80"
|
2025-08-28 18:27:07 +02:00
|
|
|
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-11-14 17:21:21 +01:00
|
|
|
<DialogContent className="flex h-full w-full max-w-full items-center justify-center border-transparent border-none bg-background p-0 md:max-w-[80vw] md:p-6 [&_#dialog-close>svg]:size-8">
|
2025-08-28 18:27:07 +02:00
|
|
|
<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) => (
|
2025-11-18 15:20:14 +01:00
|
|
|
<CarouselItem key={`${img.img}-cD`}>
|
2025-11-14 17:21:21 +01:00
|
|
|
<ImageFlbk
|
2025-08-28 18:27:07 +02:00
|
|
|
alt={`carousel-img-${idx}`}
|
2025-11-14 17:21:21 +01:00
|
|
|
className="mx-auto h-[90vh] w-full rounded-md bg-[#e6e9ec] object-contain sm:w-[80vw]"
|
2025-08-28 18:27:07 +02:00
|
|
|
height={1080}
|
2025-11-24 18:03:13 +01:00
|
|
|
src={`/storage-api/get/${img.img}?image=true&cachekey=${updated_at?.toString()}`}
|
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) => {
|
2025-09-01 16:48:03 +02:00
|
|
|
return (
|
|
|
|
|
<CarouselItem
|
|
|
|
|
className="relative flex items-center justify-center"
|
2025-11-18 15:20:14 +01:00
|
|
|
key={`carousel-videoplayer-${video.video}`}
|
2025-09-01 16:48:03 +02:00
|
|
|
>
|
|
|
|
|
<VideoPlayer
|
2025-10-10 16:18:43 +02:00
|
|
|
className="absolute mx-auto h-[90vh] w-full max-w-fit rounded-md object-contain"
|
2025-11-24 18:03:13 +01:00
|
|
|
coverImage={`/storage-api/get/${video.thumb}?image=true&cachekey=${updated_at?.toString()}`}
|
|
|
|
|
videoSrc={`/storage-api/get/${video.video}?video=true&cachekey=${updated_at?.toString()}`}
|
2025-09-01 16:48:03 +02:00
|
|
|
/>
|
|
|
|
|
</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
|
|
|
};
|