Untitled

 avatar
unknown
plain_text
5 months ago
11 kB
5
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