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

282 lines
7.4 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import { ExternalLink, UnfoldVertical } from "lucide-react";
import Link from "next/link";
import {
2025-08-28 18:27:07 +02:00
Credenza,
CredenzaBody,
CredenzaClose,
CredenzaContent,
CredenzaDescription,
CredenzaFooter,
CredenzaHeader,
CredenzaTitle,
CredenzaTrigger,
2025-08-04 17:45:44 +02:00
} from "~/components/custom_ui/credenza";
2025-08-28 18:27:07 +02:00
import { ImageFlbk } from "~/components/ImageWithFallback";
import { AnnuncioActions } from "~/components/servizio/annuncio_actions";
import { Interactions } from "~/components/servizio/interactions";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
2025-08-04 17:45:44 +02:00
import {
2025-08-28 18:27:07 +02:00
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
2025-08-04 17:45:44 +02:00
} from "~/components/ui/carousel";
2025-08-28 18:27:07 +02:00
import { handleConsegna } from "~/lib/annuncio_details";
import { cn, formatCurrency } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider";
import { useServizioAnnuncio } from "~/providers/ServizioProvider";
import type { Annunci } from "~/schemas/public/Annunci";
2025-08-04 17:45:44 +02:00
export const AnnuncioCard = ({ className }: { className?: string }) => {
2025-08-28 18:27:07 +02:00
const { data } = useServizioAnnuncio();
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
<BasicAnnuncioCard
actions={<AnnuncioActions />}
2025-08-29 16:18:32 +02:00
className={className}
data={data}
2025-08-28 18:27:07 +02:00
interactions={<Interactions />}
/>
);
2025-08-04 17:45:44 +02:00
};
export const BasicAnnuncioCard = ({
2025-08-28 18:27:07 +02:00
actions,
interactions,
className,
data,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
actions?: React.ReactNode;
interactions?: React.ReactNode;
className?: string;
data: Pick<
Annunci,
| "id"
| "codice"
| "prezzo"
| "desc_en"
| "desc_it"
| "url_immagini"
| "titolo_en"
| "titolo_it"
| "tipo"
| "consegna"
| "stato"
| "web"
>;
2025-08-04 17:45:44 +02:00
}) => {
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 (
<Card className={cn("w-full border-white py-5", className)}>
<CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 pb-0">
<CardTitle className="text-2xl">Codice: {data.codice}</CardTitle>
{actions}
</CardHeader>
<CardContent className="px-4">
<div className="flex w-full flex-col gap-4 md:flex-row md:gap-10">
<ImageFlbk
2025-08-29 16:18:32 +02:00
alt={data.codice}
className="h-auto w-full rounded-md object-contain sm:max-w-xs"
height={1080}
priority
2025-08-28 18:27:07 +02:00
src={
(data.url_immagini &&
`/go-api/images/get/${data.url_immagini[0]}`) ||
"/fallback-image.png"
}
width={1920}
/>
<div className="flex w-full flex-col gap-5">
{interactions}
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<div className="flex flex-wrap items-center gap-3">
<TipoBadge tipo={data.tipo} />
<span>
{handleConsegna({
2025-08-29 16:18:32 +02:00
aggiornamento: t.card.in_aggiornamento,
2025-08-28 18:27:07 +02:00
consegna: data.consegna,
consegna_da: t.card.consegna_da,
mesi: t.preferenze.mesi,
subito: t.card.consegna_subito,
})}
</span>
<span>{formatCurrency(data.prezzo / 1e2)}</span>
</div>
<div className="flex w-full flex-wrap items-center gap-5">
<span className="text-wrap">{data.titolo_it || data.codice}</span>
<div className="flex flex-wrap items-center gap-2">
<AnnuncioDettaglio data={data} />
{data.stato !== "Sospeso" && data.web && (
<Link
aria-label="Pagina Annuncio"
2025-08-29 16:18:32 +02:00
href={`/annuncio/${data.codice}`}
2025-08-28 18:27:07 +02:00
>
<Button
className="flex items-center gap-2"
2025-08-29 16:18:32 +02:00
variant="outline"
2025-08-28 18:27:07 +02:00
>
<ExternalLink className="size-6" />
</Button>
</Link>
)}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
2025-08-04 17:45:44 +02:00
};
const TipoBadge = ({ tipo }: { tipo: Annunci["tipo"] }) => {
2025-08-28 18:27:07 +02:00
return (
<div
className={cn(
"flex w-fit items-center justify-center rounded-md px-3",
tipo === "Transitorio" && "bg-orange-400",
tipo === "Stabile" && "bg-red-400",
tipo === "Cessione" && "bg-blue-400",
tipo === "Vendita" && "bg-green-400",
)}
>
<div className="font-semibold text-white">{tipo}</div>
</div>
);
2025-08-04 17:45:44 +02:00
};
const AnnuncioDettaglio = ({
2025-08-28 18:27:07 +02:00
data,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
data: Pick<
Annunci,
| "id"
| "codice"
| "prezzo"
| "desc_en"
| "desc_it"
| "url_immagini"
| "titolo_en"
| "titolo_it"
| "tipo"
| "consegna"
| "stato"
| "web"
>;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
const { locale, t } = useTranslation();
function replaceWithBr(text: string) {
return text.replace(/\n/g, "<br />");
}
return (
<Credenza>
<CredenzaTrigger asChild>
<Button
aria-label="Dettagli Annuncio"
2025-08-29 16:18:32 +02:00
className="flex items-center gap-2"
variant="outline"
2025-08-28 18:27:07 +02:00
>
<UnfoldVertical className="size-6" /> Dettagli annuncio
</Button>
</CredenzaTrigger>
<CredenzaContent className="max-h-[90vh] md:max-w-6xl">
<CredenzaHeader>
<CredenzaTitle className="text-xl">
Annuncio {data.codice}
</CredenzaTitle>
<CredenzaDescription className="sr-only">
Annuncio {data.codice}
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody className="scrollbar-default max-h-[80vh] overflow-y-auto pb-5">
<div className="flex flex-col gap-2 px-2">
<Carousel opts={{ loop: true }}>
<CarouselContent>
{data?.url_immagini?.map((img, idx) => (
<CarouselItem key={img}>
<ImageFlbk
alt={`${data.codice} - ${idx}`}
2025-08-29 16:18:32 +02:00
className={"h-80 w-full object-contain sm:h-[35rem]"}
2025-08-28 18:27:07 +02:00
height={200}
priority={idx === 0}
src={`/go-api/images/get/${img}`}
2025-08-29 16:18:32 +02:00
width={400}
2025-08-28 18:27:07 +02:00
/>
</CarouselItem>
))}
</CarouselContent>
<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={
!data.url_immagini || data.url_immagini.length === 1
}
/>
<CarouselNext
className="right-2 cursor-pointer"
disabled={
!data.url_immagini || data.url_immagini.length === 1
}
/>
</div>
</Carousel>
<div>
<TipoBadge tipo={data.tipo} />
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<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>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<dd className="text-lg font-semibold">
Cod: {data.codice}
</dd>
</div>
<div className="text-center">
<dt className="sr-only">{t.card.prezzo}</dt>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<dd className="text-lg font-semibold">
{formatCurrency(data.prezzo / 1e2)}
</dd>
</div>
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<div className="mb-3">
<dt className="sr-only">{t.card.titolo}</dt>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<dd className="text-base font-semibold md:text-lg">
{locale === "it" ? data.titolo_it : data.titolo_en}
</dd>
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<div className="mb-3">
<dd
className="text-base"
dangerouslySetInnerHTML={{
__html: replaceWithBr(
(locale === "it" ? data.desc_it : data.desc_it) || "",
),
}}
/>
</div>
</div>
</div>
</div>
</CredenzaBody>
<CredenzaFooter>
<CredenzaClose asChild>
<Button aria-label="Chiudi Credenza">Chiudi</Button>
</CredenzaClose>
</CredenzaFooter>
</CredenzaContent>
</Credenza>
);
2025-08-04 17:45:44 +02:00
};