infoalloggi-monorepo/apps/infoalloggi/src/components/map/Map.tsx

296 lines
6.6 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
"use client";
import {
2025-08-28 18:27:07 +02:00
Circle,
MapContainer,
Marker,
Popup,
TileLayer,
useMapEvents,
2025-08-04 17:45:44 +02:00
} from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import "leaflet-defaulticon-compatibility";
import { Home } from "lucide-react";
2025-08-28 18:27:07 +02:00
import { Marker as CustomMarker } from "~/components/map/custom_marker";
import MapMarker from "~/components/map/map_marker";
import { cn } from "~/lib/utils";
2025-08-04 17:45:44 +02:00
type LatLon = {
2025-08-28 18:27:07 +02:00
lat: number;
lng: number;
alt?: number;
2025-08-04 17:45:44 +02:00
};
type posData = {
2025-08-28 18:27:07 +02:00
pos: LatLon;
popupTxt?: string;
onClick?: () => void;
markerTxt?: string;
2025-08-04 17:45:44 +02:00
};
type GroupProps = {
2025-08-28 18:27:07 +02:00
isGroup: true;
groupData: posData[];
2025-08-04 17:45:44 +02:00
};
type SingleProps = {
2025-08-28 18:27:07 +02:00
isGroup?: false;
groupData?: never;
2025-08-04 17:45:44 +02:00
};
type MapProps = {
2025-08-28 18:27:07 +02:00
posix: posData;
autocenter?: boolean;
zoom?: number;
markerType: "precise" | "circle" | "price" | "home";
markerRadius?: number;
scrollWheelZoom?: boolean;
width?: string;
height?: string;
onMapClick?: () => void;
2025-08-04 17:45:44 +02:00
} & (GroupProps | SingleProps);
const defaults = {
2025-08-28 18:27:07 +02:00
height: "500px",
2025-08-29 16:18:32 +02:00
width: "500px",
zoom: 15,
2025-08-04 17:45:44 +02:00
};
const MedianPoint = (pos: posData[]) => {
2025-08-28 18:27:07 +02:00
const lat = pos.map((p) => p.pos.lat).reduce((a, b) => a + b) / pos.length;
const lng = pos.map((p) => p.pos.lng).reduce((a, b) => a + b) / pos.length;
return { lat, lng };
2025-08-04 17:45:44 +02:00
};
2025-08-28 18:27:07 +02:00
const MapComp = ({
posix,
autocenter,
zoom = defaults.zoom,
markerType,
markerRadius,
scrollWheelZoom = false,
width = defaults.width,
height = defaults.height,
isGroup,
groupData,
onMapClick,
2025-08-04 17:45:44 +02:00
}: MapProps) => {
2025-08-28 18:27:07 +02:00
const APIs = () => {
useMapEvents({
click: () => {
if (onMapClick) {
onMapClick();
}
},
});
return null;
};
const MarkerComp = ({ pos }: { pos: posData }) => {
switch (markerType) {
case "price":
return (
<CustomMarker
eventHandlers={pos.onClick && { click: pos.onClick }}
icon={
<MapMarker
2025-08-29 16:18:32 +02:00
color="primary"
2025-08-28 18:27:07 +02:00
label={
<span className="inline-block whitespace-nowrap">
{pos.markerTxt}
</span>
}
size="sm"
/>
}
2025-08-29 16:18:32 +02:00
position={pos.pos}
title={pos.markerTxt}
2025-08-28 18:27:07 +02:00
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</CustomMarker>
);
case "precise":
return (
<Marker
eventHandlers={pos.onClick && { click: pos.onClick }}
2025-08-29 16:18:32 +02:00
position={pos.pos}
2025-08-28 18:27:07 +02:00
title={pos.markerTxt}
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</Marker>
);
case "circle":
return (
<Circle
center={pos.pos}
2025-08-29 16:18:32 +02:00
eventHandlers={pos.onClick && { click: pos.onClick }}
2025-08-28 18:27:07 +02:00
pathOptions={{ color: "red" }}
radius={markerRadius || 100}
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</Circle>
);
case "home":
return (
<>
<Circle
center={pos.pos}
2025-08-29 16:18:32 +02:00
eventHandlers={pos.onClick && { click: pos.onClick }}
2025-08-28 18:27:07 +02:00
fillOpacity={1}
2025-08-29 16:18:32 +02:00
pathOptions={{ color: "#ec4899" }}
2025-08-28 18:27:07 +02:00
radius={markerRadius || 100}
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</Circle>
<CustomMarker
eventHandlers={pos.onClick && { click: pos.onClick }}
icon={
<div className="relative flex size-10 items-center justify-center rounded-full bg-pink-500 p-2">
<Home className="absolute size-3/4 stroke-white" />
</div>
}
2025-08-29 16:18:32 +02:00
position={pos.pos}
title={pos.markerTxt}
2025-08-28 18:27:07 +02:00
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</CustomMarker>
</>
);
}
};
const hashKey = JSON.stringify(
groupData ? groupData.map((p) => p.pos) : posix.pos,
);
return (
<MapContainer
center={
autocenter ? MedianPoint(isGroup ? groupData : [posix]) : posix.pos
}
2025-08-29 16:18:32 +02:00
className={cn("")}
key={hashKey}
2025-08-28 18:27:07 +02:00
scrollWheelZoom={scrollWheelZoom}
style={{
2025-08-29 16:18:32 +02:00
borderRadius: "8px",
2025-08-28 18:27:07 +02:00
height: height,
width: width,
}}
2025-08-29 16:18:32 +02:00
zoom={zoom}
2025-08-28 18:27:07 +02:00
>
<APIs />
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
2025-08-29 16:18:32 +02:00
//url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
url="/api/tiles/{s}/{z}/{x}/{y}"
2025-08-28 18:27:07 +02:00
/>
{isGroup ? (
// biome-ignore lint/suspicious/noArrayIndexKey: <index is acceptable here>
2025-08-28 18:27:07 +02:00
groupData.map((pos, i) => <MarkerComp key={i} pos={pos} />)
) : (
<MarkerComp pos={posix} />
)}
</MapContainer>
);
2025-08-04 17:45:44 +02:00
};
2025-08-28 18:27:07 +02:00
export default MapComp;
2025-08-04 17:45:44 +02:00
/*
const PuntiInteresse: {
title: string;
lat: number;
lng: number;
icon: IconType;
}[] = [
{
title: "PIAZZA LIBERTA",
lat: 45.766706,
lng: 11.733919,
icon: "milestone",
},
{ title: "PONTE VECCHIO", lat: 45.76752, lng: 11.731148, icon: "milestone" },
{ title: "INFOALLOGGI", lat: 45.764886, lng: 11.736019, icon: "info" },
{ title: "PRATO 1", lat: 45.770066, lng: 11.736306, icon: "parking" },
{ title: "PRATO 2", lat: 45.771495, lng: 11.734107, icon: "parking" },
{
title: "STAZIONE FERROVIARIA/BUS",
lat: 45.766709,
lng: 11.741204,
icon: "milestone",
},
{ title: "GIARDINI PAROLINI", lat: 45.764804, lng: 11.738355, icon: "park" },
{ title: "BIBLIOTECA", lat: 45.765801, lng: 11.735987, icon: "school" },
{ title: "CENTRO STUDIO", lat: 45.754266, lng: 11.733066, icon: "school" },
{ title: "OSPEDALE", lat: 45.747444, lng: 11.744371, icon: "cross" },
{
title: "PALARENA CMP",
lat: 45.747374,
lng: 11.740302,
icon: "entertainment",
},
{ title: "PONTE NUOVO", lat: 45.764594, lng: 11.729995, icon: "milestone" },
{ title: "OSSARIO ", lat: 45.763568, lng: 11.733449, icon: "milestone" },
{ title: "CINEMA METROPOLIS", lat: 45.742997, lng: 11.735622, icon: "film" },
{
title: "CENTRO COMMERCIALE GRIFONE",
lat: 45.744988,
lng: 11.751847,
icon: "cart",
},
{
title: "CENTRO COMMERCIALE EMISFERO",
lat: 45.742368,
lng: 11.728874,
icon: "cart",
},
{
title: "SUPERMERCATO Alì s. vito",
lat: 45.775167,
lng: 11.750929,
icon: "cart",
},
{
title: "SUPERMERCATO Alì cà baroncello ",
lat: 45.758659,
lng: 11.751037,
icon: "cart",
},
{
title: "CONAD via passalacqua",
lat: 45.771366,
lng: 11.742604,
icon: "cart",
},
{
title: "CONAD VIALE PECORI GIRARDI",
lat: 45.764649,
lng: 11.720416,
icon: "cart",
},
{
title: "BIOSAPORI VIALE DE GASPERI",
lat: 45.761377,
lng: 11.732186,
icon: "cart",
},
{ title: "SUPERMERCATO ALDI ", lat: 45.743694, lng: 11.755435, icon: "cart" },
{
title: "SUPERMERCATO TOSANO",
lat: 45.764982,
lng: 11.766572,
icon: "cart",
},
{
title: "POSTE CENTRALI BASSANO",
lat: 45.762758,
lng: 11.735091,
icon: "mail",
},
{ title: "POSTE VIA ANGARANO", lat: 45.767573, lng: 11.726599, icon: "mail" },
];
*/