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

162 lines
4.4 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import { AnimatePresence, motion } from "framer-motion";
2025-08-28 18:27:07 +02:00
import dynamic from "next/dynamic";
2025-08-04 17:45:44 +02:00
import Image from "next/image";
import {
2025-08-28 18:27:07 +02:00
type Dispatch,
Fragment,
memo,
type SetStateAction,
useState,
2025-08-04 17:45:44 +02:00
} from "react";
2025-08-28 18:27:07 +02:00
import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
import { handleConsegna } from "~/lib/annuncio_details";
import { formatCurrency } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider";
2025-08-04 17:45:44 +02:00
import { useRicerca } from "~/providers/RicercaProvider";
2025-08-28 18:27:07 +02:00
import type { AnnuncioRicercaWPosition } from "~/server/controllers/annunci.controller";
import { api } from "~/utils/api";
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const MapComp = dynamic(() => import("~/components/map/Map"), {
ssr: false,
2025-08-04 17:45:44 +02:00
});
export const MapSection = () => {
2025-08-28 18:27:07 +02:00
const { comune, consegna, tipo } = useRicerca();
const [selected, setSelected] = useState<AnnuncioRicercaWPosition | null>(
null,
);
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
<div className="relative z-30 flex h-[60vh] w-full items-center justify-center sm:h-[65vh]">
2025-08-28 18:27:07 +02:00
<MapDisplay
selectComune={comune}
selectConsegna={consegna}
2025-08-29 16:18:32 +02:00
selectTipo={tipo}
2025-08-28 18:27:07 +02:00
setSelected={setSelected}
/>
<SelectedComp selected={selected} />
</div>
);
2025-08-04 17:45:44 +02:00
};
const MapDisplay = memo(
2025-08-28 18:27:07 +02:00
({
selectTipo,
selectComune,
selectConsegna,
setSelected,
}: {
selectTipo: string;
selectComune: string;
selectConsegna: number | null;
setSelected: Dispatch<SetStateAction<AnnuncioRicercaWPosition | null>>;
}) => {
const { data, isLoading } = api.annunci.getMapping.useQuery({
comune: selectComune,
consegna: selectConsegna || undefined,
2025-08-29 16:18:32 +02:00
tipologia: selectTipo,
2025-08-28 18:27:07 +02:00
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
if (isLoading) return <LoadingPage />;
if (!data) return <Status500 />;
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const posizioni = data
.map((a) => {
if (a.lat_secondario && a.lon_secondario) {
return {
2025-08-29 16:18:32 +02:00
markerTxt: `${a.prezzo / 1e2}`,
onClick: () => setSelected(a),
2025-08-28 18:27:07 +02:00
pos: {
lat: parseFloat(a.lat_secondario),
lng: parseFloat(a.lon_secondario),
},
};
}
return undefined;
})
.filter((a) => a !== undefined);
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
<MapComp
groupData={posizioni}
height="100%"
2025-08-29 16:18:32 +02:00
isGroup
2025-08-28 18:27:07 +02:00
markerType="price"
onMapClick={() => setSelected(null)}
2025-08-29 16:18:32 +02:00
posix={{ pos: { lat: 45.764718, lng: 11.73067 } }}
scrollWheelZoom
width="100%"
zoom={13}
2025-08-28 18:27:07 +02:00
/>
);
},
2025-08-04 17:45:44 +02:00
);
MapDisplay.displayName = "MapDisplay";
const SelectedComp = memo(
2025-08-28 18:27:07 +02:00
({ selected }: { selected: AnnuncioRicercaWPosition | null }) => {
const { t } = useTranslation();
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
<Fragment>
<AnimatePresence initial={false}>
{selected && (
<motion.a
2025-08-29 16:18:32 +02:00
animate={{ opacity: 1, scale: 1 }}
2025-08-28 18:27:07 +02:00
aria-label="Visualizza Dettagli Annuncio"
className="group absolute right-0 bottom-0 left-0 z-20 mx-2 mb-2 flex flex-col gap-2 rounded-md bg-white p-4 ring-neutral-500 hover:ring-1"
exit={{ opacity: 0, scale: 0 }}
2025-08-29 16:18:32 +02:00
href={`/annuncio/${selected.codice}`}
initial={{ opacity: 0, scale: 0 }}
key={selected.id}
target="_blank"
2025-08-28 18:27:07 +02:00
transition={{
duration: 0.1,
2025-08-29 16:18:32 +02:00
scale: { bounce: 0.2, type: "spring", visualDuration: 0.1 },
2025-08-28 18:27:07 +02:00
}}
>
<div className="flex gap-2">
{selected.images?.[0] ? (
2025-08-28 18:27:07 +02:00
<Image
2025-08-29 16:18:32 +02:00
alt="a"
className="size-24 rounded-md object-cover sm:size-40"
height={500}
src={`/storage-api/get/${selected.images[0].img}?image=true&${selected.updated_at?.toString() || new Date().toString()}`}
2025-08-28 18:27:07 +02:00
width={500}
/>
) : (
<div className="size-20 bg-gray-200 sm:size-40" />
)}
<div className="flex flex-col sm:gap-2 sm:text-lg">
<h6 className="font-semibold underline-offset-2 group-hover:underline">
{selected.codice}
</h6>
<h3 className="text-sm sm:text-base">{selected.titolo_it}</h3>
<div className="flex gap-2">
<h3 className="font-semibold">
{formatCurrency(selected.prezzo / 1e2)}
</h3>
<h3>{selected.mq}mq</h3>
</div>
<p>
{handleConsegna({
2025-08-29 16:18:32 +02:00
aggiornamento: t.card.in_aggiornamento,
2025-08-28 18:27:07 +02:00
consegna: selected.consegna,
consegna_da: t.card.consegna_da,
mesi: t.preferenze.mesi,
subito: t.card.consegna_subito,
})}
</p>
</div>
</div>
</motion.a>
)}
</AnimatePresence>
</Fragment>
);
},
2025-08-04 17:45:44 +02:00
);
SelectedComp.displayName = "SelectedComp";