Untitled

 avatar
unknown
plain_text
5 months ago
10 kB
3
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;
}

function GoogleMapView({ errorMessage }: GoogleMapViewProps) {
  const { isLoaded, loadError } = useMap();
  const { center, setCenter } = useSearchContext();
  const { users } = useMarkerStore();

  // SEPARATE HOOKS FROM STATE
  const [userLocation, setUserLocation] =
    useState<google.maps.LatLngLiteral | null>(null);
  const [markers, setMarkers] = useState<Marker[]>([]);
  const [selectedMarker, setSelectedMarker] = useState<Marker | null>(null);

  const searchParams = useSearchParams();
  const lat = searchParams.get("lat");
  const lng = searchParams.get("lng");

  // perche' usare useMemo? puoi dichiarare direttamente l'oggetto fuori dalla funzione come costante. Non Cambierà mai, e puoi tranquillamente usarlo come costante

  const mapOptions = useMemo(
    () => ({
      styles: mapStyles,
      language: "it",
      region: "IT",
      clickableIcons: false,
      zoomControl: true,
      streetViewControl: false,
      fullscreenControl: false,
      mapTypeControl: false,
      gestureHandling: "greedy",
    }),
    []
  );

  useEffect(() => {
    if (lat && lng) {
      setCenter({ lat: parseFloat(lat), lng: parseFloat(lng) });
    } else if (navigator.geolocation) {
      // THIS FAIL SILENTLY!!! RETURN ERROR TO AT LEAST U!
      navigator.geolocation.getCurrentPosition(
        (position) => {
          const location = {
            lat: position.coords.latitude,
            lng: position.coords.longitude,
          };
          setCenter(location);
          setUserLocation(location);
        },
        (error) => {
          console.error("Error getting user's location:", error);
        }
      );
    }
  }, [lat, lng, setCenter]);

  useEffect(() => {
    if (isLoaded && users.length > 0) {
      const geocoder = new google.maps.Geocoder();
      const markersWithCoordinates = users.map(
        (user, index) =>
          // I FEEL LIKE THIS HAS SOME LOGIC ISSUES, IF THE ADDRESS IS NOT FOUND, IT WILL BE NULL, AND THE FILTER WILL REMOVE IT
          new Promise<Marker>((resolve) => {
            geocoder.geocode(
              { address: user.fullAddress },
              (results, status) => {
                if (status === "OK" && results && results[0]) {
                  resolve({
                    id: `${user.referral_code}_${user.address}_${user.cap}_${index}`,
                    name: user.company_name,
                    address: user.fullAddress,
                    lat: results[0].geometry.location.lat(),
                    lng: results[0].geometry.location.lng(),
                    referral_code: user.referral_code,
                  });
                } else {
                  console.error(
                    "Geocode was not successful for the following reason:",
                    status
                  );
                  resolve({
                    id: `${user.referral_code}_${user.address}_${user.cap}_${index}`,
                    name: user.company_name,
                    address: user.fullAddress,
                    referral_code: user.referral_code,
                  });
                }
              }
            );
          })
      );
      Promise.all(markersWithCoordinates).then((updatedMarkers) => {
        setMarkers(updatedMarkers.filter((marker) => marker.lat && marker.lng));
      });
    } else {
      setMarkers([]);
    }
  }, [isLoaded, users]);

  const handleMarkerClick = useCallback((marker: Marker) => {
    setSelectedMarker(marker);
  }, []);

  // ABSTRACT THIS FUNCTION OUTSIDE THE COMPONENT IN A UTILS! NO NEED TO RECREATE IT EVERY RENDER
  const calculateDistance = 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);
  };

  useEffect(() => {
    // THIS IS NOT READABLE,MORE: U CAN USE CN TO HANDLE THIS, AND USE A CLASSNAME TO STYLE THE INFO WINDOW
    if (isLoaded) {
      const removeInfoWindowPadding = () => {
        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";
        });
      };

      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
// USE A SEPARATE COMPONENT
      {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