infoalloggi-monorepo/apps/infoalloggi/src/components/annuncio_card.tsx

310 lines
10 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import Link from "next/link";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "~/components/ui/carousel";
import { cn, formatCurrency } from "~/lib/utils";
import { BedDouble, CalendarClock, Maximize2, Ruler } from "lucide-react";
import Image from "next/image";
import { useTranslation } from "~/providers/I18nProvider";
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
import { camereTesti, handleConsegna } from "~/lib/annuncio_details";
import { ImageFlbk } from "~/components/ImageWithFallback";
type CardAnnuncioProps = {
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;
};
const CardAnnuncio = ({
id,
codice,
prezzo,
titolo,
mq,
comune,
provincia,
consegna,
camere,
immagini,
tipo,
stato,
className,
}: CardAnnuncioProps) => {
const { t } = useTranslation();
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",
2025-08-04 17:45:44 +02:00
className,
)}
id={`card-annuncio-${id}`}
>
<Link
href={`/annuncio/${codice}`}
//target="_blank"
className="block p-2" //duration-700 ease-in-out animate-in fade-in
2025-08-04 17:45:44 +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>
)}
<Carousel opts={{ loop: true }}>
<CarouselContent>
{immagini &&
immagini.map((img, idx) => (
<CarouselItem key={`${img}-${idx}`}>
<ImageFlbk
alt={t.card.alt_immagine}
width={800}
height={400}
src={img}
priority={idx === 0}
className={
"h-56 w-full rounded-xl object-cover outline outline-neutral-200"
}
/>
</CarouselItem>
))}
</CarouselContent>
<div
onKeyDown={(e) => e.stopPropagation()}
role="button"
tabIndex={0}
onClick={(e) => e.preventDefault()}
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-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",
2025-08-04 17:45:44 +02:00
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">
2025-08-04 17:45:44 +02:00
<div className="flex flex-row justify-around">
<div className="text-center">
<span className="sr-only">{t.card.codice}</span>
<span className="text-xl font-semibold text-red-600">
2025-08-04 17:45:44 +02:00
Cod: {codice}
</span>
</div>
<div className="text-center">
<span className="sr-only">{t.card.prezzo}</span>
<span className="text-xl font-semibold">
2025-08-04 17:45:44 +02:00
{formatCurrency(prezzo / 1e2)}
</span>
</div>
</div>
<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
<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>
<span className="text-sm font-medium">
{comune && provincia && `${comune} (${provincia})`}
</span>
2025-08-04 17:45:44 +02:00
</div>
</div>
<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">
2025-08-04 17:45:44 +02:00
<Ruler className="size-4 text-indigo-700" />
<div>
<p className="font-medium">
2025-08-04 17:45:44 +02:00
{mq ? Number.parseFloat(mq) : 0}{" "}
<span>
2025-08-04 17:45:44 +02:00
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">
2025-08-04 17:45:44 +02:00
<BedDouble className="size-4 text-indigo-700" />
<div>
<p className="font-medium">
2025-08-04 17:45:44 +02:00
{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">
2025-08-04 17:45:44 +02:00
<CalendarClock className="size-4 text-indigo-700" />
<div>
<p className="truncate font-medium">
2025-08-04 17:45:44 +02:00
{handleConsegna({
consegna: consegna,
consegna_da: t.card.consegna_da,
mesi: t.preferenze.mesi,
subito: t.card.consegna_subito,
aggiornamento: t.card.in_aggiornamento,
})}
</p>
</div>
</div>
</div>
</Link>
</div>
);
};
export default CardAnnuncio;
export const CarouselAnnuncio = ({
single,
immagini,
}: {
single?: boolean;
immagini: string[] | null;
}) => {
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">
<Carousel opts={{ loop: true }}>
<CarouselContent>
{immagini &&
immagini.map((img, idx) => (
<CarouselItem
key={`${img}-${idx}-cF`}
className={cn(
"group relative",
immagini && immagini.length === 1
? ""
: !single && "md:basis-1/2 lg:basis-1/3",
)}
>
<ImageFlbk
priority={idx === 0}
alt={img}
width={800}
height={400}
onClick={() => handleOpenModal(idx)}
src={img}
className={cn(
"h-72 w-full rounded-md object-cover",
immagini.length === 1 && "object-contain",
)}
/>
<Maximize2
onClick={() => handleOpenModal(idx)}
className="absolute right-2 bottom-2 size-6 rounded-md bg-white/70 stroke-2 p-1 transition-opacity duration-150 group-hover:opacity-100 sm:opacity-0"
/>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious
type="button"
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1}
/>
<CarouselNext
type="button"
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1}
/>
</Carousel>
</div>
</div>
<Dialog open={openModal} onOpenChange={setOpenModal}>
<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 &&
immagini.map((img, idx) => (
<CarouselItem key={`${img}-${idx}-cD`}>
<Image
alt={`carousel-img-${idx}`}
width={1920}
height={1080}
src={img}
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]"
/>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious
type="button"
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1}
/>
<CarouselNext
type="button"
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1}
/>
</Carousel>
</DialogContent>
</Dialog>
</>
);
};