"use client" import { useMap } from "@vis.gl/react-google-maps" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useIntl } from "react-intl" import { useMediaQuery } from "usehooks-ts" import { selectHotel } from "@/constants/routes/hotelReservation" import { useBookingCodeFilterStore } from "@/stores/bookingCode-filter" import { useHotelFilterStore } from "@/stores/hotel-filters" import { useHotelsMapStore } from "@/stores/hotels-map" import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton" import { CloseIcon, CloseLargeIcon } from "@/components/Icons" import InteractiveMap from "@/components/Maps/InteractiveMap" import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton" import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import useLang from "@/hooks/useLang" import { useScrollToTop } from "@/hooks/useScrollToTop" import { debounce } from "@/utils/debounce" import BookingCodeFilter from "../../BookingCodeFilter" import FilterAndSortModal from "../../FilterAndSortModal" import HotelListing from "../HotelListing" import { getVisibleHotels } from "./utils" import styles from "./selectHotelMapContent.module.css" import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { SelectHotelMapProps } from "@/types/components/hotelReservation/selectHotel/map" const SKELETON_LOAD_DELAY = 750 export default function SelectHotelContent({ hotelPins, cityCoordinates, mapId, hotels, filterList, bookingCode, }: Omit) { const lang = useLang() const intl = useIntl() const map = useMap() const isAboveMobile = useMediaQuery("(min-width: 768px)") const [visibleHotels, setVisibleHotels] = useState([]) const [showSkeleton, setShowSkeleton] = useState(true) const listingContainerRef = useRef(null) const activeFilters = useHotelFilterStore((state) => state.activeFilters) const { activeHotelCard, activeHotelPin } = useHotelsMapStore() const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490, elementRef: listingContainerRef, refScrollable: true, }) const activeCodeFilter = useBookingCodeFilterStore( (state) => state.activeCodeFilter ) const coordinates = useMemo( () => isAboveMobile ? cityCoordinates : { ...cityCoordinates, lat: cityCoordinates.lat - 0.006 }, [isAboveMobile, cityCoordinates] ) useEffect(() => { if (listingContainerRef.current) { const activeElement = listingContainerRef.current.querySelector(`[data-active="true"]`) if (activeElement) { activeElement.scrollIntoView({ behavior: "smooth", block: "nearest" }) } } }, [activeHotelCard, activeHotelPin]) const filteredHotelPins = useMemo(() => { const updatedHotelsList = bookingCode ? hotelPins.filter( (hotel) => !hotel.publicPrice || activeCodeFilter === "all" || (activeCodeFilter === "discounted" && hotel.rateType?.toLowerCase() !== "regular") || activeCodeFilter === hotel.rateType?.toLowerCase() ) : hotelPins return updatedHotelsList.filter((hotel) => activeFilters.every((filterId) => hotel.facilityIds.includes(Number(filterId)) ) ) }, [activeFilters, hotelPins, bookingCode, activeCodeFilter]) const getHotelCards = useCallback(() => { const visibleHotels = getVisibleHotels(hotels, filteredHotelPins, map) setVisibleHotels(visibleHotels) setTimeout(() => { setShowSkeleton(false) }, SKELETON_LOAD_DELAY) }, [hotels, filteredHotelPins, map]) /** * Updates visible hotels when map viewport changes (zoom/pan) * - Debounces updates to prevent excessive re-renders during map interaction * - Shows loading skeleton while map tiles load * - Triggers on: initial load, zoom, pan, and tile loading completion */ const debouncedUpdateHotelCards = useMemo( () => debounce(() => { if (!map) return if (isAboveMobile) { setShowSkeleton(true) } getHotelCards() }, 100), [map, getHotelCards, isAboveMobile] ) const closeButton = ( ) return (
{bookingCode ? : null}
{showSkeleton ? (
) : ( )} {showBackToTop && ( )}
) }