diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/HotelListItem/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/HotelListItem/index.tsx index 8d97c99c2..d80613eaf 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/HotelListItem/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/CityMap/HotelListItem/index.tsx @@ -32,10 +32,10 @@ export default function HotelListItem({ hotel, url }: HotelListItemProps) { const amenities = hotel.detailedFacilities.slice(0, 5) const itemRef = useRef(null) - const { setHoveredHotel, activeHotel } = useDestinationPageHotelsMapStore() + const { setHoveredMarker, activeMarker } = useDestinationPageHotelsMapStore() useEffect(() => { - if (activeHotel === hotel.operaId) { + if (activeMarker === hotel.operaId) { const element = itemRef.current if (element) { element.scrollIntoView({ @@ -45,24 +45,24 @@ export default function HotelListItem({ hotel, url }: HotelListItemProps) { }) } } - }, [activeHotel, hotel.operaId]) + }, [activeMarker, hotel.operaId]) const handleMouseEnter = useCallback(() => { if (hotel.operaId) { - setHoveredHotel(hotel.operaId) + setHoveredMarker(hotel.operaId) } - }, [setHoveredHotel, hotel.operaId]) + }, [setHoveredMarker, hotel.operaId]) const handleMouseLeave = useCallback(() => { - setHoveredHotel(null) - }, [setHoveredHotel]) + setHoveredMarker(null) + }, [setHoveredMarker]) return (
marker.id === activeMarker) + + if (!activeHotel) { + return null + } + + return ( + + ) +} diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/OverviewMapContainer/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/OverviewMapContainer/index.tsx index 2da8a46af..9a86a55fa 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/OverviewMapContainer/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationOverviewPage/OverviewMapContainer/index.tsx @@ -5,6 +5,7 @@ import DynamicMap from "../../Map/DynamicMap" import MapContent from "../../Map/MapContent" import MapProvider from "../../Map/MapProvider" import { getHotelMapMarkers, mapMarkerDataToGeoJson } from "../../Map/utils" +import ActiveMapCard from "./ActiveMapCard" import InputForm from "./InputForm" import type { MapLocation } from "@/types/components/mapLocation" @@ -43,8 +44,10 @@ export default async function OverviewMapContainer({ markers={markers} defaultCoordinates={defaultCoordinates} defaultZoom={defaultZoom} + gestureHandling="cooperative" > + ) diff --git a/apps/scandic-web/components/ContentType/DestinationPage/HotelCardCarousel/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/HotelCardCarousel/index.tsx index 3ed9ee42e..e102ae0d2 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/HotelCardCarousel/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/HotelCardCarousel/index.tsx @@ -19,19 +19,19 @@ interface MapCardCarouselProps { export default function HotelCardCarousel({ visibleHotels, }: MapCardCarouselProps) { - const { activeHotel, setActiveHotel } = useDestinationPageHotelsMapStore() + const { activeMarker, setActiveMarker } = useDestinationPageHotelsMapStore() const selectedHotelIdx = visibleHotels.findIndex( - ({ hotel }) => hotel.operaId === activeHotel + ({ hotel }) => hotel.operaId === activeMarker ) const handleScrollSelect = useCallback( (idx: number) => { if (selectedHotelIdx !== -1) { - setActiveHotel(visibleHotels[idx]?.hotel.operaId) + setActiveMarker(visibleHotels[idx]?.hotel.operaId) } }, - [setActiveHotel, visibleHotels, selectedHotelIdx] + [setActiveMarker, visibleHotels, selectedHotelIdx] ) return ( @@ -47,7 +47,7 @@ export default function HotelCardCarousel({ (null) @@ -108,7 +108,7 @@ export default function HotelListingItem({ setActiveHotel(hotel.operaId)} + onClick={() => setActiveMarker(hotel.operaId)} > {intl.formatMessage({ id: "See on map" })} diff --git a/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/hotelListing.module.css b/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/hotelListing.module.css index a19f8bf1c..65003aaeb 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/hotelListing.module.css +++ b/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/hotelListing.module.css @@ -9,8 +9,14 @@ } .listHeader { + display: grid; + gap: var(--Space-x2); +} + +.cta { display: flex; justify-content: space-between; + gap: var(--Spacing-x2); } .hotelList { @@ -26,3 +32,14 @@ ); } } + +@media screen and (min-width: 1367px) { + .listHeader { + display: flex; + justify-content: space-between; + } + + .mapButton { + display: none !important; /* Important to override button higher specificy */ + } +} diff --git a/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/index.tsx index 0849df736..55bf835f4 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/HotelListing/index.tsx @@ -1,13 +1,17 @@ "use client" -import { useRef } from "react" +import Link from "next/link" +import { useParams } from "next/navigation" +import { useEffect, useRef, useState } from "react" import { useIntl } from "react-intl" import { useDestinationDataStore } from "@/stores/destination-data" import DestinationFilterAndSort from "@/components/DestinationFilterAndSort" +import { MapIcon } from "@/components/Icons" import Alert from "@/components/TempDesignSystem/Alert" import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton" +import Button from "@/components/TempDesignSystem/Button" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import { useScrollToTop } from "@/hooks/useScrollToTop" @@ -21,6 +25,8 @@ import { AlertTypeEnum } from "@/types/enums/alert" export default function HotelListing() { const intl = useIntl() const scrollRef = useRef(null) + const params = useParams() + const [mapUrl, setMapUrl] = useState(null) const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 300, elementRef: scrollRef, @@ -30,6 +36,12 @@ export default function HotelListing() { isLoading: state.isLoading, })) + useEffect(() => { + const url = new URL(window.location.href) + url.searchParams.set("view", "map") + setMapUrl(url.toString()) + }, [params]) + return isLoading ? ( ) : ( @@ -43,7 +55,24 @@ export default function HotelListing() { { count: activeHotels.length } )} - +
+ {mapUrl && ( + + )} + +
{activeHotels.length === 0 ? ( void } @@ -36,10 +41,23 @@ export default function DynamicMap({ defaultZoom, fitBounds = true, onClose, + gestureHandling = "auto", children, }: PropsWithChildren) { const intl = useIntl() const map = useMap() + const pageType = usePageType() + const { activeMarker } = useDestinationPageHotelsMapStore() + const ref = useRef(null) + + useEffect(() => { + if (ref.current && activeMarker && pageType === "overview") { + ref.current.scrollIntoView({ + behavior: "smooth", + block: "end", + }) + } + }, [activeMarker, pageType]) useEffect(() => { if (map && fitBounds) { @@ -81,12 +99,12 @@ export default function DynamicMap({ defaultZoom, disableDefaultUI: true, clickableIcons: false, - gestureHandling: "greedy", + gestureHandling, mapId, } return ( -
+
Unable to display map}> {children} diff --git a/apps/scandic-web/components/ContentType/DestinationPage/Map/MapContent/ClusterMarker/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/Map/MapContent/ClusterMarker/index.tsx index b45164b37..56113cb8f 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/Map/MapContent/ClusterMarker/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/Map/MapContent/ClusterMarker/index.tsx @@ -27,10 +27,10 @@ export default function ClusterMarker({ onMarkerClick, hotelIds, }: ClusterMarkerProps) { - const { hoveredHotel, activeHotel } = useDestinationPageHotelsMapStore() + const { hoveredMarker, activeMarker } = useDestinationPageHotelsMapStore() const isActive = - hotelIds.includes(Number(hoveredHotel)) || - hotelIds.includes(Number(activeHotel)) + hotelIds.includes(Number(hoveredMarker)) || + hotelIds.includes(Number(activeMarker)) const handleClick = useCallback(() => { if (onMarkerClick) { diff --git a/apps/scandic-web/components/ContentType/DestinationPage/Map/MapContent/Marker/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/Map/MapContent/Marker/index.tsx index 8be23def5..d711fdfac 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/Map/MapContent/Marker/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/Map/MapContent/Marker/index.tsx @@ -5,7 +5,6 @@ import { AdvancedMarkerAnchorPoint, useAdvancedMarkerRef, } from "@vis.gl/react-google-maps" -import { useCallback } from "react" import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map" @@ -24,30 +23,30 @@ interface MarkerProps { export default function Marker({ position, properties }: MarkerProps) { const [markerRef] = useAdvancedMarkerRef() - const { setHoveredHotel, setActiveHotel, hoveredHotel, activeHotel } = + const { setHoveredMarker, setActiveMarker, hoveredMarker, activeMarker } = useDestinationPageHotelsMapStore() - const handleClick = useCallback(() => { - setActiveHotel(properties.id) + function handleMarkerClick() { + setActiveMarker(properties.id) trackMapClick(properties.name) - }, [setActiveHotel, properties]) + } - const handleMouseEnter = useCallback(() => { - setHoveredHotel(properties.id) - }, [setHoveredHotel, properties.id]) + function handleMouseEnter() { + setHoveredMarker(properties.id) + } - const handleMouseLeave = useCallback(() => { - setHoveredHotel(null) - }, [setHoveredHotel]) + function handleMouseLeave() { + setHoveredMarker(null) + } - const isHovered = hoveredHotel === properties.id - const isActive = activeHotel === properties.id + const isHovered = hoveredMarker === properties.id + const isActive = activeMarker === properties.id return ( ( - geojson, - CLUSTER_OPTIONS - ) + const { clusters, containedHotels, getClusterZoom } = + useSupercluster(geojson, CLUSTER_OPTIONS) // Based on the length of active filters, we decide if should show clusters or individual markers const markerList = hasActiveFilters ? geojson.features : clusters useEffect(() => { map?.addListener("click", () => { - if (activeHotel) { - setActiveHotel(null) + if (activeMarker) { + setActiveMarker(null) } }) - }, [activeHotel, map, setActiveHotel]) + }, [activeMarker, map, setActiveMarker]) - function handleClusterClick(position: google.maps.LatLngLiteral) { + function handleClusterClick( + position: google.maps.LatLngLiteral, + clusterProperties: ClusterProperties + ) { const currentZoom = map && map.getZoom() + const clusterZoom = getClusterZoom(clusterProperties.cluster_id) + if (currentZoom) { map.panTo(position) - map.setZoom(currentZoom + 2) + map.setZoom(clusterZoom ?? currentZoom + 2) } } @@ -73,7 +76,9 @@ export default function MapContent({ position={{ lat, lng }} size={clusterProperties.point_count} sizeAsText={String(clusterProperties.point_count_abbreviated)} - onMarkerClick={handleClusterClick} + onMarkerClick={(position) => + handleClusterClick(position, clusterProperties) + } hotelIds={containedHotels[idx]} /> ) : ( diff --git a/apps/scandic-web/components/ContentType/DestinationPage/Map/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/Map/index.tsx index 5abadea5e..8fdbe99c6 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/Map/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/Map/index.tsx @@ -48,7 +48,7 @@ export default function Map({ }: PropsWithChildren) { const router = useRouter() const searchParams = useSearchParams() - const { activeHotel: activeHotelId } = useDestinationPageHotelsMapStore() + const { activeMarker: activeHotelId } = useDestinationPageHotelsMapStore() const isMapView = useMemo( () => searchParams.get("view") === "map", [searchParams] @@ -169,6 +169,7 @@ export default function Map({ defaultCoordinates={defaultCoordinates} defaultZoom={defaultZoom} fitBounds={!activeHotel} + gestureHandling="greedy" > ( return clusterer.getClusters(bbox, zoom) }, [version, clusterer, bbox, zoom]) + function getClusterZoom(clusterId: number) { + if (!clusterer || version === 0) { + return null + } + + return clusterer.getClusterExpansionZoom(clusterId) + } + // retrieve the hotel ids included in the cluster const containedHotels = clusters.map((cluster) => { if (cluster.properties?.cluster && typeof cluster.id === "number") { @@ -47,5 +55,6 @@ export function useSupercluster( return { clusters, containedHotels, + getClusterZoom, } } diff --git a/apps/scandic-web/stores/destination-page-hotels-map.ts b/apps/scandic-web/stores/destination-page-hotels-map.ts index 67872860d..297244c99 100644 --- a/apps/scandic-web/stores/destination-page-hotels-map.ts +++ b/apps/scandic-web/stores/destination-page-hotels-map.ts @@ -1,16 +1,16 @@ import { create } from "zustand" interface DestinationPageHotelsMapState { - hoveredHotel: string | null - activeHotel: string | null - setHoveredHotel: (hotelId: string | null) => void - setActiveHotel: (hotelId: string | null) => void + hoveredMarker: string | null + activeMarker: string | null + setHoveredMarker: (hotelId: string | null) => void + setActiveMarker: (hotelId: string | null) => void } export const useDestinationPageHotelsMapStore = create((set) => ({ - hoveredHotel: null, - activeHotel: null, - setHoveredHotel: (hotelId) => set({ hoveredHotel: hotelId }), - setActiveHotel: (hotelId) => set({ activeHotel: hotelId }), + hoveredMarker: null, + activeMarker: null, + setHoveredMarker: (hotelId) => set({ hoveredMarker: hotelId }), + setActiveMarker: (hotelId) => set({ activeMarker: hotelId }), }))