2025-08-04 17:45:44 +02:00
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Armchair,
|
|
|
|
|
Bath,
|
|
|
|
|
BedDouble,
|
|
|
|
|
Building2,
|
|
|
|
|
Car,
|
|
|
|
|
Clock,
|
|
|
|
|
Copy,
|
|
|
|
|
Edit,
|
|
|
|
|
ExternalLink,
|
|
|
|
|
Ruler,
|
|
|
|
|
Share2,
|
2025-08-04 17:45:44 +02:00
|
|
|
} from "lucide-react";
|
|
|
|
|
import type { GetStaticPaths, GetStaticPropsContext, NextPage } from "next";
|
|
|
|
|
import Head from "next/head";
|
|
|
|
|
import Link from "next/link";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import toast from "react-hot-toast";
|
|
|
|
|
import { CarouselAnnuncio } from "~/components/annuncio_card";
|
|
|
|
|
import { AnnuncioInteractions } from "~/components/annuncio-interactions/annuncio_interactions";
|
|
|
|
|
import { TouchProvider } from "~/components/custom_ui/HybridTooltip";
|
|
|
|
|
import Input from "~/components/custom_ui/input";
|
|
|
|
|
import FailedAnnuncioLoading from "~/components/failed-loading";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { IconMatrix, type IconType } from "~/components/IconComponents";
|
2025-08-28 18:27:07 +02:00
|
|
|
import InformationBubble from "~/components/InformationBubble";
|
2025-08-22 16:27:58 +02:00
|
|
|
import { LoadingPage } from "~/components/loading";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { MappaDialogFullscreen } from "~/components/MapDialog";
|
|
|
|
|
import { PricingComponent } from "~/components/prezzi";
|
2025-08-28 18:27:07 +02:00
|
|
|
import {
|
|
|
|
|
Accordion,
|
|
|
|
|
AccordionContent,
|
|
|
|
|
AccordionItem,
|
|
|
|
|
AccordionTrigger,
|
|
|
|
|
} from "~/components/ui/accordion";
|
|
|
|
|
import { Button, buttonVariants } from "~/components/ui/button";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
|
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogTrigger,
|
2025-08-04 17:45:44 +02:00
|
|
|
} from "~/components/ui/dialog";
|
|
|
|
|
import { Label } from "~/components/ui/label";
|
|
|
|
|
import { VideoPlayer } from "~/components/videoPlayer";
|
2025-08-08 10:27:21 +02:00
|
|
|
import { env } from "~/env.mjs";
|
2025-08-28 18:27:07 +02:00
|
|
|
import {
|
|
|
|
|
type CaratteristicheFiltered,
|
|
|
|
|
filteredCaratteristiche,
|
|
|
|
|
} from "~/hooks/schedaAnnuncioUtils";
|
|
|
|
|
import { useMediaQuery } from "~/hooks/use-media-query";
|
|
|
|
|
import { handleConsegna } from "~/lib/annuncio_details";
|
|
|
|
|
import { cn } from "~/lib/utils";
|
|
|
|
|
import { AnnuncioContext } from "~/providers/AnnuncioProvider";
|
|
|
|
|
import { useTranslation } from "~/providers/I18nProvider";
|
|
|
|
|
import { useSession } from "~/providers/SessionProvider";
|
2025-08-08 11:34:33 +02:00
|
|
|
import type { Annunci } from "~/schemas/public/Annunci";
|
2025-08-28 18:27:07 +02:00
|
|
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
|
|
|
|
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
|
|
|
|
import { api } from "~/utils/api";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
type AnnuncioProps = {
|
2025-08-28 18:27:07 +02:00
|
|
|
cod: string;
|
|
|
|
|
flag: string;
|
|
|
|
|
meta: {
|
|
|
|
|
ogImage: string;
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
|
|
|
|
ogUrl: string;
|
|
|
|
|
} | null;
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
cod,
|
|
|
|
|
flag,
|
|
|
|
|
meta,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: AnnuncioProps) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<main>
|
|
|
|
|
<Head>
|
|
|
|
|
<title>{`${cod} | ${meta?.title}`}</title>
|
|
|
|
|
<meta property="description" content={meta?.description} />
|
|
|
|
|
<meta property="og:url" content={meta?.ogUrl} />
|
|
|
|
|
<meta property="og:type" content="website" />
|
|
|
|
|
<meta property="og:title" content={`${cod} | ${meta?.title}`} />
|
|
|
|
|
<meta property="og:description" content={meta?.description} />
|
|
|
|
|
<meta property="og:image" content={meta?.ogImage} />
|
|
|
|
|
<meta property="og:image:width" content="1200" />
|
|
|
|
|
<meta property="og:image:height" content="630" />
|
|
|
|
|
<meta
|
|
|
|
|
property="og:logo"
|
|
|
|
|
content={`${env.NEXT_PUBLIC_BASE_URL}/Infoalloggi.png`}
|
|
|
|
|
/>
|
|
|
|
|
<meta property="og:site_name" content="Infoalloggi.it" />
|
|
|
|
|
</Head>
|
|
|
|
|
<AnnuncioView cod={cod} flag={flag} />
|
|
|
|
|
</main>
|
|
|
|
|
);
|
2025-08-08 14:13:27 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const session = useSession();
|
|
|
|
|
const { locale } = useTranslation();
|
|
|
|
|
|
|
|
|
|
const { data, isLoading } = api.annunci.getAnnuncio.useQuery(
|
|
|
|
|
{
|
|
|
|
|
cod: cod,
|
|
|
|
|
},
|
|
|
|
|
{ refetchOnMount: false },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const isDesktop = useMediaQuery("(min-width: 768px)");
|
|
|
|
|
|
|
|
|
|
if (isLoading || session.status === "pending") return <LoadingPage />;
|
|
|
|
|
if (
|
|
|
|
|
!data ||
|
|
|
|
|
session.status === "error" ||
|
|
|
|
|
(data && data.stato === "Sospeso")
|
|
|
|
|
) {
|
|
|
|
|
return <FailedAnnuncioLoading />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const titolo = (locale === "it" ? data.titolo_it : data.titolo_en) || "";
|
|
|
|
|
const description = (locale === "it" ? data.desc_it : data.desc_en) || "";
|
|
|
|
|
|
|
|
|
|
const filteredCaratt = data.caratteristiche
|
|
|
|
|
? filteredCaratteristiche(data.caratteristiche)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<AnnuncioContext.Provider value={data}>
|
|
|
|
|
<TouchProvider>
|
|
|
|
|
<div className="mx-auto w-full px-2 sm:px-8">
|
|
|
|
|
<CarouselAnnuncio immagini={data.url_immagini} />
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col sm:py-8 md:flex-row-reverse md:gap-2">
|
|
|
|
|
<div className="mb-6 flex flex-col md:mb-0 md:w-1/3">
|
|
|
|
|
<div className="flex w-full flex-col flex-nowrap items-center justify-center gap-6">
|
|
|
|
|
<CardInfos data={data} />
|
|
|
|
|
{session.user?.isAdmin ? (
|
|
|
|
|
<Link
|
|
|
|
|
href={`/area-riservata/admin/edit-annuncio/${data.id}`}
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex w-full flex-row gap-4",
|
|
|
|
|
buttonVariants({ variant: "default" }),
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<Edit className="size-5" /> Modifica annuncio
|
|
|
|
|
</Link>
|
|
|
|
|
) : (
|
|
|
|
|
<AnnuncioInteractions
|
|
|
|
|
session={session}
|
|
|
|
|
disabled={flag === "true"}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{data.tipo &&
|
|
|
|
|
(data.tipo === "Transitorio" || data.tipo === "Stabile") && (
|
|
|
|
|
<>
|
|
|
|
|
<Pricing
|
|
|
|
|
tipologia={
|
|
|
|
|
data.tipo === "Transitorio"
|
|
|
|
|
? TipologiaPosizioneEnum.Transitorio
|
|
|
|
|
: TipologiaPosizioneEnum.Stabile
|
|
|
|
|
}
|
|
|
|
|
isDesktop={isDesktop}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mx-2 md:w-2/3">
|
|
|
|
|
<TestiAnnuncio
|
|
|
|
|
titolo={titolo}
|
|
|
|
|
description={description}
|
|
|
|
|
filteredCaratt={filteredCaratt}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<AnnuncioFooter
|
|
|
|
|
tipo={data.tipo}
|
|
|
|
|
comune={data.comune}
|
|
|
|
|
consegna={data.consegna}
|
|
|
|
|
url_video={data.url_video || []}
|
|
|
|
|
first_image={data.url_immagini?.[0] ? data.url_immagini[0] : ""}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</TouchProvider>
|
|
|
|
|
</AnnuncioContext.Provider>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export async function getStaticProps(
|
2025-08-28 18:27:07 +02:00
|
|
|
context: GetStaticPropsContext<{ cod: string }>,
|
2025-08-04 17:45:44 +02:00
|
|
|
) {
|
2025-08-28 18:27:07 +02:00
|
|
|
const ssg = generateSSGHelper();
|
|
|
|
|
const cod = context.params?.cod;
|
|
|
|
|
if (typeof cod !== "string") {
|
|
|
|
|
return {
|
|
|
|
|
notFound: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
await ssg.annunci.getAnnuncio.prefetch({ cod: cod });
|
|
|
|
|
|
|
|
|
|
const meta = await ssg.annunci.getAnnuncioMeta.fetch({ cod: cod });
|
|
|
|
|
|
|
|
|
|
const flag = await ssg.settings.GetFlagValue.fetch({
|
|
|
|
|
id: "ANNUNCIO_INTERACTIONS_DISABLED",
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
props: {
|
|
|
|
|
trpcState: ssg.dehydrate(),
|
|
|
|
|
cod,
|
|
|
|
|
flag,
|
|
|
|
|
meta,
|
|
|
|
|
},
|
|
|
|
|
revalidate: 1,
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const getStaticPaths: GetStaticPaths = async () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
return {
|
|
|
|
|
paths: [],
|
|
|
|
|
fallback: "blocking",
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default AnnuncioDettaglio;
|
|
|
|
|
|
|
|
|
|
const Pricing = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
isDesktop,
|
|
|
|
|
tipologia,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
isDesktop: boolean;
|
|
|
|
|
tipologia: TipologiaPosizioneEnum;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
const { data: prezziario, isLoading } =
|
|
|
|
|
api.prezziario.getPrezziPerTipologia.useQuery({
|
|
|
|
|
tipologia,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (isLoading) return <LoadingPage />;
|
|
|
|
|
if (!prezziario) return null;
|
|
|
|
|
|
|
|
|
|
const cta =
|
|
|
|
|
tipologia === TipologiaPosizioneEnum.Transitorio
|
|
|
|
|
? t.pricing_cmp.cta_breve
|
|
|
|
|
: t.pricing_cmp.cta_stabile;
|
|
|
|
|
const opzioni =
|
|
|
|
|
tipologia === TipologiaPosizioneEnum.Transitorio
|
|
|
|
|
? [
|
|
|
|
|
t.pricing_cmp.breve_option1,
|
|
|
|
|
t.pricing_cmp.breve_option2,
|
|
|
|
|
t.pricing_cmp.breve_option3,
|
|
|
|
|
t.pricing_cmp.breve_option4,
|
|
|
|
|
t.pricing_cmp.breve_option5,
|
|
|
|
|
]
|
|
|
|
|
: [t.pricing_cmp.stabile_option1, t.pricing_cmp.stabile_option2];
|
|
|
|
|
|
|
|
|
|
const style =
|
|
|
|
|
tipologia === TipologiaPosizioneEnum.Transitorio
|
|
|
|
|
? "border-transitorio bg-transitorio"
|
|
|
|
|
: "border-stabile bg-stabile";
|
|
|
|
|
|
|
|
|
|
const pricing = prezziario.saldi.map((s) => ({
|
|
|
|
|
downPayment: prezziario.acconto.prezzo_cent / 100,
|
|
|
|
|
secondPayment: s.prezzo_cent / 100,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
if (isDesktop) {
|
|
|
|
|
return (
|
|
|
|
|
<PricingComponent
|
|
|
|
|
cta={cta}
|
|
|
|
|
pricingData={pricing}
|
|
|
|
|
opzioni={opzioni}
|
|
|
|
|
statico={true}
|
|
|
|
|
className={cn(
|
|
|
|
|
style,
|
|
|
|
|
"[&_[data-role=amount]]:text-2xl xl:[&_[data-role=amount]]:text-3xl [&_[data-role=title]]:text-2xl xl:[&_[data-role=title]]:text-3xl [&_[data-role=wrapper]]:flex-wrap lg:[&_[data-role=wrapper]]:flex-nowrap",
|
|
|
|
|
)}
|
|
|
|
|
title={
|
|
|
|
|
tipologia === TipologiaPosizioneEnum.Transitorio
|
|
|
|
|
? t.prezzi.titolo_transitori
|
|
|
|
|
: t.prezzi.titolo_stabile
|
|
|
|
|
}
|
|
|
|
|
tipo={tipologia}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
return (
|
|
|
|
|
<Accordion type="single" collapsible className="w-full">
|
|
|
|
|
<AccordionItem value="item-1">
|
|
|
|
|
<AccordionTrigger className="px-1 text-xl font-semibold">
|
|
|
|
|
{t.pricing_cmp.cta_costo}
|
|
|
|
|
</AccordionTrigger>
|
|
|
|
|
<AccordionContent className="flex justify-center">
|
|
|
|
|
<PricingComponent
|
|
|
|
|
cta={cta}
|
|
|
|
|
pricingData={pricing}
|
|
|
|
|
opzioni={opzioni}
|
|
|
|
|
statico={true}
|
|
|
|
|
className={cn(style)}
|
|
|
|
|
title={
|
|
|
|
|
tipologia === TipologiaPosizioneEnum.Transitorio
|
|
|
|
|
? t.prezzi.titolo_transitori
|
|
|
|
|
: t.prezzi.titolo_stabile
|
|
|
|
|
}
|
|
|
|
|
tipo={tipologia}
|
|
|
|
|
/>
|
|
|
|
|
</AccordionContent>
|
|
|
|
|
</AccordionItem>
|
|
|
|
|
</Accordion>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const TestiAnnuncio = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
titolo,
|
|
|
|
|
description,
|
|
|
|
|
filteredCaratt,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
titolo: string;
|
|
|
|
|
description: string;
|
|
|
|
|
filteredCaratt:
|
|
|
|
|
| {
|
|
|
|
|
text: string;
|
|
|
|
|
icon: IconType;
|
|
|
|
|
value: string | null;
|
|
|
|
|
}[]
|
|
|
|
|
| null;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
function replaceWithBr(text: string) {
|
|
|
|
|
return text.replace(/\n/g, "<br />");
|
|
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<h1 className="text-3xl font-bold uppercase">{titolo}</h1>
|
|
|
|
|
<h1
|
|
|
|
|
className="py-6"
|
|
|
|
|
dangerouslySetInnerHTML={{
|
|
|
|
|
__html: replaceWithBr(description),
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
{filteredCaratt && (
|
|
|
|
|
<CaratteristicheAccordion filteredCaratt={filteredCaratt} />
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const CaratteristicheAccordion = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
filteredCaratt,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
filteredCaratt: CaratteristicheFiltered;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<Accordion
|
|
|
|
|
className="my-5"
|
|
|
|
|
type="single"
|
|
|
|
|
collapsible
|
|
|
|
|
defaultValue="altre-caratt"
|
|
|
|
|
>
|
|
|
|
|
<AccordionItem value="altre-caratt">
|
|
|
|
|
<AccordionTrigger>Altre caratteristiche</AccordionTrigger>
|
|
|
|
|
<AccordionContent>
|
|
|
|
|
{Object.entries(filteredCaratt).map(([key, scheda_value]) => {
|
|
|
|
|
const { text, icon, value } = scheda_value;
|
|
|
|
|
if (value === null) return null;
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={key}
|
|
|
|
|
className="flex flex-row items-center gap-2 text-base"
|
|
|
|
|
>
|
|
|
|
|
<IconMatrix type={icon} className="size-4" />
|
|
|
|
|
{text}: {value}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</AccordionContent>
|
|
|
|
|
</AccordionItem>
|
|
|
|
|
</Accordion>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-08 11:34:33 +02:00
|
|
|
const CardInfos = ({ data }: { data: Annunci }) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Card className="relative w-full">
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="text-center text-3xl font-bold md:pl-8 lg:pl-0">
|
|
|
|
|
Codice {data.codice}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="flex flex-col gap-4 p-4 py-0">
|
|
|
|
|
<div className="grid grid-cols-2 gap-3 font-semibold">
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<div className="rounded-md bg-neutral-100 p-2 text-center">
|
|
|
|
|
{data.prezzo && (data.prezzo / 1e2).toFixed(2).replace(".", ",")}{" "}
|
|
|
|
|
€/mese
|
|
|
|
|
</div>
|
|
|
|
|
{data.prezzo && (
|
|
|
|
|
<InformationBubble
|
|
|
|
|
icon="euro"
|
|
|
|
|
iconClassName="h-[19.5px] w-[19.5px] bg-orange-500 text-white rounded-full"
|
|
|
|
|
>
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-primary text-sm font-semibold">
|
|
|
|
|
Prezzo al giorno
|
|
|
|
|
</span>
|
|
|
|
|
<div className="text-muted-foreground text-sm">
|
|
|
|
|
{(data.prezzo / 1e2 / 30).toFixed(2).replace(".", ",")} €/gg
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</InformationBubble>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"rounded-md bg-neutral-100 p-2 text-center outline-2",
|
|
|
|
|
data.tipo === "Transitorio" && "outline-transitorio",
|
|
|
|
|
data.tipo === "Stabile" && "outline-stabile",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
Affitto {data.tipo}{" "}
|
|
|
|
|
</div>
|
|
|
|
|
{data.tipo === "Transitorio" && (
|
|
|
|
|
<InformationBubble
|
|
|
|
|
href="/guida#faq-transitori"
|
|
|
|
|
target="_blank"
|
|
|
|
|
txt="Più informazioni sugli affitti transitori"
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<h3 className="px-2">Fornitura:</h3>
|
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
|
|
|
<div className="flex justify-center gap-2 rounded-md bg-neutral-100 p-2">
|
|
|
|
|
<BedDouble />
|
|
|
|
|
{data.numero_camere && data.numero_camere
|
|
|
|
|
? data.numero_camere > 1
|
|
|
|
|
? `${data.numero_camere} ${t.card.camere2}`
|
|
|
|
|
: `${data.numero_camere} ${t.card.camere1}`
|
|
|
|
|
: "N/A"}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-center gap-2 rounded-md bg-neutral-100 p-2">
|
|
|
|
|
<Bath />
|
|
|
|
|
<span>{data.numero_bagni} bagni</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-center gap-2 rounded-md bg-neutral-100 p-2">
|
|
|
|
|
<Armchair />
|
|
|
|
|
{data.caratteristiche?.Scheda_Arredi || ""}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-center gap-2 rounded-md bg-neutral-100 p-2">
|
|
|
|
|
<Car />
|
|
|
|
|
<span>{data.numero_postiauto} posti auto</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<h3 className="px-2">Dati immobile:</h3>
|
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
|
|
|
<div className="flex justify-center gap-2 rounded-md bg-neutral-100 p-2">
|
|
|
|
|
<Ruler />
|
|
|
|
|
<span>
|
|
|
|
|
{data.mq} m<sup>2</sup>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-center gap-2 rounded-md bg-neutral-100 p-2">
|
|
|
|
|
<Building2 />
|
|
|
|
|
<span>
|
|
|
|
|
{data.piano && data.piano === "0"
|
|
|
|
|
? "piano terra"
|
|
|
|
|
: data.piano === "0.5"
|
|
|
|
|
? "piano rialz."
|
|
|
|
|
: `${data.piano}° piano`}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-center text-base font-bold text-neutral-700">
|
|
|
|
|
{data.stato === "Trattativa" ? (
|
|
|
|
|
"Annuncio in aggiornamento"
|
|
|
|
|
) : (
|
|
|
|
|
// <span className="flex items-center justify-center gap-1">
|
|
|
|
|
// <Clock className="inline-block size-5" />
|
|
|
|
|
// Disponibilità:{" "}
|
|
|
|
|
// <span className="text-red-500">
|
|
|
|
|
// {handleConsegna({
|
|
|
|
|
// consegna: data.consegna,
|
|
|
|
|
// consegna_da: t.card.consegna_da,
|
|
|
|
|
// mesi: t.preferenze.mesi,
|
|
|
|
|
// subito: t.card.consegna_subito,
|
|
|
|
|
// aggiornamento: t.card.in_aggiornamento,
|
|
|
|
|
// })}
|
|
|
|
|
// </span>
|
|
|
|
|
// </span>
|
|
|
|
|
<div className="flex items-center justify-center gap-1 rounded-md py-1.5 outline-2 outline-neutral-500">
|
|
|
|
|
<Clock className="inline-block size-5" />
|
|
|
|
|
Disponibilità:{" "}
|
|
|
|
|
<span className="text-red-500">
|
|
|
|
|
{handleConsegna({
|
|
|
|
|
consegna: data.consegna,
|
|
|
|
|
consegna_da: t.card.consegna_da,
|
|
|
|
|
mesi: t.preferenze.mesi,
|
|
|
|
|
subito: t.card.consegna_subito,
|
|
|
|
|
aggiornamento: t.card.in_aggiornamento,
|
|
|
|
|
})}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-row justify-around gap-3">
|
|
|
|
|
<MappaDialogFullscreen
|
|
|
|
|
lat={data.lat_secondario}
|
|
|
|
|
long={data.lon_secondario}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<ShareComponent />
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ShareComponent = () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
const [link, setLink] = useState("");
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (typeof window !== "undefined") {
|
|
|
|
|
setLink(window.location.href);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
const handleCopyLink = async () => {
|
|
|
|
|
if (navigator.clipboard?.writeText) {
|
|
|
|
|
try {
|
|
|
|
|
await navigator.clipboard.writeText(link);
|
|
|
|
|
toast(t.share.copiato, { icon: "📋" });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to copy: ", err);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Fallback for browsers that do not support navigator.clipboard.writeText
|
|
|
|
|
const textArea = document.createElement("textarea");
|
|
|
|
|
textArea.value = link;
|
|
|
|
|
document.body.appendChild(textArea);
|
|
|
|
|
textArea.select();
|
|
|
|
|
try {
|
|
|
|
|
document.execCommand("copy");
|
|
|
|
|
toast(t.share.copiato, { icon: "📋" });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to copy: ", err);
|
|
|
|
|
}
|
|
|
|
|
document.body.removeChild(textArea);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Dialog>
|
|
|
|
|
<DialogTrigger asChild>
|
|
|
|
|
<Button variant="outline">
|
|
|
|
|
<Share2 className="size-5" />
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogTrigger>
|
|
|
|
|
<DialogContent className="sm:max-w-md">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>{t.share.condividi}</DialogTitle>
|
|
|
|
|
<DialogDescription>{t.share.descrizione}</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
<div className="grid flex-1 gap-2">
|
|
|
|
|
<Label htmlFor="link" className="sr-only">
|
|
|
|
|
Link
|
|
|
|
|
</Label>
|
|
|
|
|
<Input id="link" defaultValue={link} readOnly />
|
|
|
|
|
</div>
|
|
|
|
|
<Button
|
|
|
|
|
type="submit"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="px-3"
|
|
|
|
|
onClick={async () => await handleCopyLink()}
|
|
|
|
|
>
|
|
|
|
|
<span className="sr-only">Copy</span>
|
|
|
|
|
<Copy />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AnnuncioFooter = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
tipo,
|
|
|
|
|
comune,
|
|
|
|
|
consegna,
|
|
|
|
|
url_video,
|
|
|
|
|
first_image,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
tipo: string | null;
|
|
|
|
|
comune: string | null;
|
|
|
|
|
consegna: number | null;
|
|
|
|
|
url_video: string[];
|
|
|
|
|
first_image: string;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<p className="px-2 text-lg font-semibold">Video:</p>
|
|
|
|
|
<div className="flex flex-col flex-wrap gap-4 sm:flex-row sm:gap-2 sm:px-2">
|
|
|
|
|
{url_video.map((video) => {
|
|
|
|
|
if (!video) return null;
|
|
|
|
|
if (video.includes("youtu")) {
|
|
|
|
|
return null;
|
|
|
|
|
/*return (
|
2025-08-04 17:45:44 +02:00
|
|
|
<iframe
|
|
|
|
|
title="video"
|
|
|
|
|
className={cn("h-96 w-auto rounded-md")}
|
|
|
|
|
src={video}
|
|
|
|
|
//src={`https://www.youtube-nocookie.com/embed/${video.split(/[/ ]+/).pop()}`}
|
|
|
|
|
allow=" autoplay; encrypted-media"
|
|
|
|
|
allowFullScreen
|
|
|
|
|
key={`ytplayer-${video}`}
|
|
|
|
|
/>
|
|
|
|
|
);*/
|
2025-08-28 18:27:07 +02:00
|
|
|
} else {
|
|
|
|
|
return (
|
|
|
|
|
<VideoPlayer
|
|
|
|
|
key={`videoplayer-${video}`}
|
|
|
|
|
coverImage={first_image}
|
|
|
|
|
videoSrc={video}
|
|
|
|
|
className="h-96 max-w-96"
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Link
|
|
|
|
|
href={{
|
|
|
|
|
pathname: "/annunci",
|
|
|
|
|
query: {
|
|
|
|
|
tipo: tipo,
|
|
|
|
|
comune: comune,
|
|
|
|
|
consegna: consegna,
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
target="_blank"
|
|
|
|
|
>
|
|
|
|
|
<Button className="my-10 flex w-full items-center gap-2 bg-neutral-500 text-xl">
|
|
|
|
|
Cerca annunci simili <ExternalLink className="size-6" />
|
|
|
|
|
</Button>
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|