Untitled
unknown
plain_text
a year ago
11 kB
8
Indexable
"use client";
//IMPORT LIKE THIS REACT => React.useState | React.useEffect
import * as React from "react";
import { GoogleMap, MarkerF, InfoWindow } from "@react-google-maps/api";
import { mapStyles } from "@/lib/mapStyles";
import { useSearchContext } from "@/context/SearchContext";
import { useSearchParams } from "next/navigation";
import useMarkerStore from "@/store/markers-store";
import { useMap } from "@/context/MapContext";
import BeeSpinner from "../BeeSpinner";
import { Button } from "@/components/ui/button";
import ModernInfoWindow from "../ModernInfoWindow";
interface Marker {
id: string;
name: string;
address: string;
lat?: number;
lng?: number;
referral_code: string;
}
interface GoogleMapViewProps {
errorMessage?: string | null;
}
const mapOptions = {
styles: mapStyles,
language: "it",
region: "IT",
clickableIcons: false,
zoomControl: true,
streetViewControl: false,
fullscreenControl: false,
mapTypeControl: false,
gestureHandling: "greedy",
};
type User = {
referral_code: string;
company_name: string;
address: string;
cap: string;
fullAddress: string;
};
// ABSTRACT THIS FUNCTION OUTSIDE THE COMPONENT IN A UTILS! NO NEED TO RECREATE IT EVERY RENDER
const calculateDistance = React.useCallback(
(lat1: number, lon1: number, lat2: number, lon2: number) => {
const R = 6371;
const dLat = deg2rad(lat2 - lat1);
const dLon = deg2rad(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(deg2rad(lat1)) *
Math.cos(deg2rad(lat2)) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c;
return d;
},
[]
);
// SAME AS ABOVE, MORE ADD A ERROR OF SOME KM CAUSE THIS IS HAVERSINE FORMULA, AND IT DOESNT ACCOUNT ROUTES, JUST STRAIGHT LINE
const deg2rad = (deg: number) => {
return deg * (Math.PI / 180);
};
const removeInfoWindowPadding = () => {
if (!document) return;
const infoWindows = document.querySelectorAll(".gm-style-iw-c");
infoWindows.forEach((iw) => {
(iw as HTMLElement).style.padding = "0";
(iw as HTMLElement).style.maxWidth = "none";
(iw as HTMLElement).style.maxHeight = "none";
(iw as HTMLElement).style.overflow = "visible";
(iw as HTMLElement).style.background = "none";
(iw as HTMLElement).style.boxShadow = "none";
});
const infoWindowContents = document.querySelectorAll(".gm-style-iw-d");
infoWindowContents.forEach((iwc) => {
(iwc as HTMLElement).style.padding = "0";
(iwc as HTMLElement).style.maxWidth = "none";
(iwc as HTMLElement).style.maxHeight = "none";
(iwc as HTMLElement).style.overflow = "visible";
});
const infoWindowBackground = document.querySelector(".gm-style-iw-t");
if (infoWindowBackground) {
(infoWindowBackground as HTMLElement).style.background = "none";
}
// Remove the default close button
const closeButtons = document.querySelectorAll(".gm-ui-hover-effect");
closeButtons.forEach((button) => {
(button as HTMLElement).style.display = "none";
});
// Remove the arrow
const arrows = document.querySelectorAll(".gm-style-iw-tc");
arrows.forEach((arrow) => {
(arrow as HTMLElement).style.display = "none";
});
};
function GoogleMapView({ errorMessage }: GoogleMapViewProps) {
const { isLoaded, loadError } = useMap();
const { center, setCenter } = useSearchContext();
// Users presumo siano i marker, ma non è chiaro, sarebbe meglio chiamarli markers o userMarkers
const { users } = useMarkerStore();
// SEPARATE HOOKS FROM STATE
const [userLocation, setUserLocation] =
React.useState<google.maps.LatLngLiteral | null>(null);
const [markers, setMarkers] = React.useState<Marker[]>([]);
const [selectedMarker, setSelectedMarker] = React.useState<Marker | null>(
null
);
const searchParams = useSearchParams();
const lat = searchParams.get("lat");
const lng = searchParams.get("lng");
const getLocation = React.useCallback(() => {
try {
if (navigator.geolocation) {
return navigator.geolocation.getCurrentPosition(
(position) => {
setUserLocation({
lat: position.coords.latitude,
lng: position.coords.longitude,
});
setCenter({
lat: position.coords.latitude,
lng: position.coords.longitude,
});
},
(error) => {
throw new Error(error.message);
}
);
}
} catch (error) {
console.error("Error getting user's location:", error);
}
}, [setCenter]);
React.useEffect(() => {
if (lat && lng) {
setCenter({ lat: Number(lat), lng: Number(lng) });
} else {
getLocation();
}
}, [getLocation, lat, lng, setCenter]);
const geocoder = new google.maps.Geocoder();
// VA DA UN ALTRA PARTE
const parseGeocodeResult = (
user: User,
results: google.maps.GeocoderResult,
index: number
): Marker => {
return {
id: `${user.referral_code}_${user.address}_${user.cap}_${index}`,
name: user.company_name,
address: user.fullAddress,
lat: results.geometry.location.lat(),
lng: results.geometry.location.lng(),
referral_code: user.referral_code,
};
};
const fetchMarkers = React.useCallback(async () => {
const geocodeUser = async (user: User, index: number): Promise<Marker> => {
return new Promise((resolve) => {
geocoder.geocode({ address: user.fullAddress }, (results, status) => {
if (status === "OK" && results?.[0]) {
resolve(parseGeocodeResult(user, results[0], index));
} else {
console.error("Geocode failed:", status);
resolve({
id: `${user.referral_code}_${user.address}_${user.cap}_${index}`,
name: user.company_name,
address: user.fullAddress,
referral_code: user.referral_code,
lat: undefined,
lng: undefined,
});
}
});
});
};
if (isLoaded && users.length > 0) {
const markersWithCoordinates = users.map(
(user: User, index: number): Promise<Marker> => geocodeUser(user, index)
);
Promise.all(markersWithCoordinates).then((updatedMarkers) => {
setMarkers(updatedMarkers.filter((marker) => marker.lat && marker.lng));
});
} else {
setMarkers([]);
}
}, [isLoaded, users]);
React.useEffect(() => {
if (isLoaded && users.length > 0) {
const markersWithCoordinates = users.map(
(user: User, index: number): Promise<Marker> => geocodeUser(user, index)
);
Promise.all(markersWithCoordinates).then((updatedMarkers) => {
setMarkers(updatedMarkers.filter((marker) => marker.lat && marker.lng));
});
} else {
setMarkers([]);
}
}, [isLoaded, users]);
const handleMarkerClick = useCallback((marker: Marker) => {
setSelectedMarker(marker);
}, []);
React.useEffect(() => {
// THIS IS NOT READABLE,MORE: U CAN USE CN TO HANDLE THIS, AND USE A CLASSNAME TO STYLE THE INFO WINDOW
if (isLoaded) {
removeInfoWindowPadding();
const observer = new MutationObserver(removeInfoWindowPadding);
observer.observe(document.body, { childList: true, subtree: true });
return () => observer.disconnect();
}
}, [isLoaded, selectedMarker]);
if (loadError)
return <div>Errore nel caricamento di Maps. Ricarica la pagina.</div>;
if (!isLoaded)
return (
<div className="flex items-center justify-center h-[calc(100dvh-80px)] w-full">
<BeeSpinner />
</div>
);
let infoWindowOptions = {};
if (isLoaded) {
infoWindowOptions = {
pixelOffset: new google.maps.Size(0, -30),
maxWidth: 256,
disableAutoPan: false,
};
}
// THOSE ARE CONSTANT AND SHOULD BE OUTSIDE THE COMPONENT OR EVEN ASTRACTED
const defaultIcon = {
url: "/assets/icons/Mapmarkerviola.svg",
scaledSize: new google.maps.Size(50, 50),
};
const selectedIcon = {
url: "/assets/icons/Mapmarkergiallo.svg",
scaledSize: new google.maps.Size(55, 55),
};
return (
<div className="flex flex-col h-full w-full relative">
<GoogleMap
mapContainerClassName="flex-1 lg:rounded-[1rem] lg:h-full rounded-none h-[calc(100vh-4rem)]"
center={center}
zoom={14}
options={mapOptions}
>
{userLocation && (
<MarkerF position={userLocation} title="Your Location" />
)}
{markers.map(
(marker) =>
marker.lat &&
marker.lng && (
<MarkerF
key={marker.id}
position={{ lat: marker.lat, lng: marker.lng }}
title={`${marker.name} - ${marker.address}`}
icon={
selectedMarker?.id === marker.id ? selectedIcon : defaultIcon
}
onClick={() => handleMarkerClick(marker)}
/>
)
)}
{selectedMarker && selectedMarker.lat && selectedMarker.lng && (
<InfoWindow
position={{ lat: selectedMarker.lat, lng: selectedMarker.lng }}
onCloseClick={() => setSelectedMarker(null)}
options={infoWindowOptions}
>
<div className="w-64 h-80 overflow-hidden rounded-lg">
<ModernInfoWindow
name={selectedMarker.name}
address={selectedMarker.address}
onClose={() => setSelectedMarker(null)}
distance={
userLocation
? calculateDistance(
userLocation.lat,
userLocation.lng,
selectedMarker.lat,
selectedMarker.lng
)
: null
}
referralCode={selectedMarker.referral_code}
/>
</div>
</InfoWindow>
)}
</GoogleMap>
{errorMessage && (
<div className="absolute inset-0 z-[10] flex items-center justify-center bg-black/75 backdrop-blur-sm lg:rounded-[1rem]">
<div className="bg-white p-6 rounded-lg shadow-lg max-w-md text-center">
<p className="text-red-600 font-semibold text-xl mb-2">
Ops! Qualcosa è andato storto.
</p>
<p className="mb-4">
Errore nel caricamento dei marker. Ricarica la pagina.
</p>
<Button
variant="nodropButton"
onClick={() => window.location.reload()}
className="text-white px-6 py-2 rounded-[10px] transition-colors"
>
Ricarica la pagina
</Button>
</div>
</div>
)}
</div>
);
}
export default GoogleMapView;
Editor is loading...
Leave a Comment