import { Armchair, ArrowUp01, BadgeEuro, Bath, BedDouble, Car, Clock, Copy, Edit, ExternalLink, Printer, Ruler, Share2, TrafficCone, } from "lucide-react"; import type { GetStaticPaths, GetStaticPropsContext, NextPage } from "next"; import Head from "next/head"; import Link from "next/link"; 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 { ComeFunziona } from "~/components/expand_guida"; import FailedAnnuncioLoading from "~/components/failed-loading"; import { IconMatrix } from "~/components/IconComponents"; import InformationBubble from "~/components/InformationBubble"; import { LoadingPage } from "~/components/loading"; import { MappaDialogFullscreen } from "~/components/MapDialog"; import { PricingComponent, type PricingData } from "~/components/prezzi"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "~/components/ui/accordion"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "~/components/ui/collapsible"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "~/components/ui/dialog"; import Input from "~/components/ui/input"; import { Label } from "~/components/ui/label"; import { VideoPlayer } from "~/components/videoPlayer"; import { env } from "~/env"; import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils"; import { useStaleImageReload } from "~/hooks/staleImage"; import { useMediaQuery } from "~/hooks/use-media-query"; import { handleConsegna } from "~/lib/annuncio_details"; import { replaceWithBr } from "~/lib/newlineToBr"; import { getStorageUrl } from "~/lib/storage_utils"; import { cn } from "~/lib/utils"; import { AnnuncioContext, useAnnuncio } from "~/providers/AnnuncioProvider"; import { useTranslation } from "~/providers/I18nProvider"; import { buildRicercaUrl } from "~/providers/RicercaProvider"; import { useSession } from "~/providers/SessionProvider"; import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum"; import type { AnnuncioData } from "~/server/controllers/annunci.controller"; import { generateSSGHelper } from "~/server/utils/ssgHelper"; import { api } from "~/utils/api"; type AnnuncioProps = { data: AnnuncioData; flag: string; }; /** * Pagina di dettaglio annuncio: /annuncio/[cod] * Questa pagina mostra i dettagli di un annuncio specifico, identificato dal codice (cod) passato come parametro. Utilizza getStaticProps per prefetchare i dati dell'annuncio e le relative informazioni meta per SEO e condivisione sui social media. Se l'annuncio è in stato "Sospeso" o se si verifica un errore durante il caricamento, viene mostrata una pagina di errore. */ const AnnuncioDettaglio: NextPage = ({ data, flag, }: AnnuncioProps) => { return (
{`${data.codice} | ${data.titolo_it}`}
); }; const AnnuncioView = ({ data, flag }: AnnuncioProps) => { const { status, user } = useSession(); useStaleImageReload(); const isDesktop = useMediaQuery("(min-width: 768px)"); if ( !data || status !== "AUTHENTICATED" || (data && data.stato === "Sospeso") ) { return ; } return (
{user?.isAdmin ? ( <> ) : ( )} {data.tipo && (data.tipo === "Transitorio" || data.tipo === "Stabile") && ( <> )}
); }; export async function getStaticProps( context: GetStaticPropsContext<{ cod: string }>, ) { const ssg = generateSSGHelper(); const cod = context.params?.cod; if (typeof cod !== "string") { return { redirect: { destination: "/annuncio-non-trovato", permanent: false, }, }; } const annuncio = await ssg.annunci.getAnnuncioData.fetch({ cod: cod }); if (!annuncio.success || annuncio.data.stato === "Sospeso") { return { redirect: { destination: "/annuncio-non-trovato", permanent: false, }, }; } const flag = await ssg.flags.GetFlagValue.fetch({ id: "ANNUNCIO_INTERACTIONS_DISABLED", }); return { props: { data: annuncio.data, //cod, flag, //meta, trpcState: ssg.dehydrate(), }, revalidate: 21600, // 6 hours - ensures fresh data 4x per day }; } export const getStaticPaths: GetStaticPaths = async () => { return { fallback: "blocking", paths: [], }; }; export default AnnuncioDettaglio; const Pricing = ({ isDesktop }: { isDesktop: boolean }) => { const { t } = useTranslation(); const { prezzo, tipo } = useAnnuncio(); const tipologia = tipo === "Transitorio" ? TipologiaPosizioneEnum.Transitorio : TipologiaPosizioneEnum.Stabile; const { data: prezziario, isLoading } = api.prezziario.getPrezziPerTipologia.useQuery({ tipologia, }); if (isLoading) return ; if (!prezziario) return null; const opzioniT = [ 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, ]; const opzioniS = [ t.pricing_cmp.stabile_option1, t.pricing_cmp.stabile_option2, t.pricing_cmp.stabile_option3, ]; const cta = tipologia === TipologiaPosizioneEnum.Transitorio ? t.pricing_cmp.cta_breve : t.pricing_cmp.cta_stabile; const pricing = prezziario.saldi.map((s) => ({ downPayment: prezziario.acconto.prezzo_cent / 100, secondPayment: s.prezzo_cent / 100, })); let opzioni: string[] = []; let pricingData: PricingData = []; if (tipologia === TipologiaPosizioneEnum.Stabile) { //se stabile, mostra il prezzo basato sul canone in default const idx = prezzo > 70000 ? 2 : prezzo > 50000 ? 1 : 0; const opzione = opzioniS[idx]; const pricingS = pricing[idx]; if (opzione && pricingS) { opzioni = [opzione]; pricingData = [pricingS]; } else { opzioni = opzioniS; pricingData = pricing; } } else { opzioni = opzioniT; pricingData = pricing; } const style = tipologia === TipologiaPosizioneEnum.Transitorio ? "border-transitorio bg-transitorio" : "border-stabile bg-stabile"; const title = tipologia === TipologiaPosizioneEnum.Transitorio ? t.prezzi.titolo_transitori : t.prezzi.titolo_stabile; if (isDesktop) { return ( ); } return ( ); }; const TestiAnnuncio = () => { const { locale } = useTranslation(); const { titolo_it, titolo_en, desc_it, desc_en } = useAnnuncio(); return ( <>

{locale === "it" ? titolo_it : titolo_en}

); }; const CaratteristicheAccordion = () => { const { caratteristiche } = useAnnuncio(); if (!caratteristiche) return null; return ( Altre caratteristiche {Object.entries(filteredCaratteristiche(caratteristiche)).map( ([key, scheda_value]) => { const { text, icon, value } = scheda_value; if (value === null) return null; return (
{text}: {value}
); }, )}
); }; const CardInfos = () => { const { t } = useTranslation(); const { codice, prezzo, tipo, numero_camere, numero_bagni, caratteristiche, numero_postiauto, mq, piano, stato, consegna, lat_secondario, lon_secondario, } = useAnnuncio(); return ( {t.annunci.codice} {codice}
{prezzo && (prezzo / 1e2).toFixed(2).replace(".", ",")}{" "} {t.annunci.euro_mese}
{prezzo && (
{t.annunci.prezzo_giorno}
{(prezzo / 1e2 / 30).toFixed(2).replace(".", ",")}{" "} {t.annunci.euro_giorno}
)}
{tipo}
{tipo === "Transitorio" && ( )}

{t.annunci.fornitura}:

{(() => { if (!numero_camere) return "N/A"; if (numero_camere > 1) return `${numero_camere} ${t.card.camere2}`; if (numero_camere === 1) return `${numero_camere} ${t.card.camere1}`; })()}
{numero_bagni || 0} {t.annunci.bagni}
{caratteristiche?.Scheda_Arredi || ""}
{numero_postiauto || 0} {t.annunci.posti_auto}

{t.annunci.dati_immobile}:

{mq ? Number(mq).toLocaleString("it-IT") : "--"} m2
{piano !== null && piano !== "" && ( <> {(() => { if (piano === "0") return t.annunci.p_terra; if (piano === "0.5") return t.annunci.p_rialzato; if (piano) return `${piano}° ${t.annunci.piano}`; return "--"; })()} )}
{stato === "Trattativa" ? (
{t.annunci.in_aggiornamento}
) : (
{t.annunci.disponibile}:{" "} {handleConsegna({ aggiornamento: t.card.in_aggiornamento, consegna: consegna, consegna_da: t.card.consegna_da, mesi: t.parametri.mesi, subito: t.card.consegna_subito, })}
)}
); }; const ShareComponent = () => { 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 ( {t.share.condividi} {t.share.descrizione}
); }; const AnnuncioFooter = () => { const { tipo, comune, consegna, videos, external_videos, media_updated_at } = useAnnuncio(); const { t } = useTranslation(); return (

Video:

{videos.map((video) => { return ( ); })} {external_videos?.map((videoUrl) => { const embedUrl = getYouTubeEmbedUrl(videoUrl); if (!embedUrl) return null; return (