"use client" import { useRouter, useSearchParams } from "next/navigation" import { useEffect, useMemo, useRef } from "react" import { useIntl } from "react-intl" import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType" import { alternativeHotelsMap, selectHotelMap, } from "@scandic-hotels/common/constants/routes/hotelReservation" import { useScrollToTop } from "@scandic-hotels/common/hooks/useScrollToTop" import { BackToTopButton } from "@scandic-hotels/design-system/BackToTopButton" import { HotelCard } from "@scandic-hotels/design-system/HotelCard" import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext" import { useIsLoggedIn } from "../../hooks/useIsLoggedIn" import useLang from "../../hooks/useLang" import { mapApiImagesToGalleryImages } from "../../misc/imageGallery" import { BookingCodeFilterEnum, useBookingCodeFilterStore, } from "../../stores/bookingCode-filter" import { useHotelResultCountStore } from "../../stores/hotel-result-count" import { useHotelsMapStore } from "../../stores/hotels-map" import { HotelDetailsSidePeek } from "../HotelDetailsSidePeek" import { useHotelFilters } from "../SelectHotel/Filters/useHotelFilters" import { DEFAULT_SORT } from "../SelectHotel/HotelSorter" import { getSortedHotels } from "./utils" import styles from "./hotelCardListing.module.css" import type { HotelType } from "@scandic-hotels/common/constants/hotelType" import type { HotelResponse } from "../SelectHotel/helpers" export enum HotelCardListingTypeEnum { MapListing = "mapListing", PageListing = "pageListing", } type HotelCardListingProps = { hotelData: HotelResponse[] type?: HotelCardListingTypeEnum isAlternative?: boolean } export default function HotelCardListing({ hotelData, type = HotelCardListingTypeEnum.PageListing, isAlternative, }: HotelCardListingProps) { const router = useRouter() const lang = useLang() const intl = useIntl() const isUserLoggedIn = useIsLoggedIn() const searchParams = useSearchParams() const [activeFilters] = useHotelFilters(null) const setResultCount = useHotelResultCountStore( (state) => state.setResultCount ) const { activeHotel, activate, disengage, engage } = useHotelsMapStore() const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490 }) const activeCardRef = useRef(null) const config = useBookingFlowConfig() const sortBy = searchParams.get("sort") ?? DEFAULT_SORT const bookingCode = searchParams.get("bookingCode") // Special rates (corporate cheque, voucher) will not show regular rate hotels availability const isSpecialRate = bookingCode ? hotelData.find( (hotel) => hotel.availability.productType?.bonusCheque || hotel.availability.productType?.voucher ) : false const activeCodeFilter = useBookingCodeFilterStore( (state) => state.activeCodeFilter ) const isBookingCodeRateAvailable = bookingCode && !isSpecialRate ? hotelData.some((hotel) => hotel.availability.bookingCode) : false const showOnlyBookingCodeRates = isBookingCodeRateAvailable && activeCodeFilter === BookingCodeFilterEnum.Discounted const hotelsWithBookingCode = hotelData.filter( (hotel) => hotel.availability?.bookingCode ) const isCampaignWithBookingCode = !!bookingCode && hotelsWithBookingCode.length > 0 && hotelsWithBookingCode.every( (hotel) => hotel.availability.productType?.public?.rateType === RateTypeEnum.PublicPromotion || hotel.availability.productType?.member?.rateType === RateTypeEnum.PublicPromotion ) const unfilteredHotelCount = showOnlyBookingCodeRates ? hotelData.filter((hotel) => hotel.availability.bookingCode).length : hotelData.length const hotels = useMemo(() => { const sortedHotels = getSortedHotels({ hotels: hotelData, sortBy, bookingCode: isSpecialRate ? null : bookingCode, }) const updatedHotelsList = showOnlyBookingCodeRates ? sortedHotels.filter((hotel) => hotel.availability.bookingCode) : sortedHotels if (!activeFilters.length) { return updatedHotelsList } return updatedHotelsList.filter((hotel) => activeFilters.every((appliedFilterId) => hotel.hotel.detailedFacilities.some( (facility) => facility.id.toString() === appliedFilterId ) ) ) }, [ activeFilters, bookingCode, hotelData, sortBy, showOnlyBookingCodeRates, isSpecialRate, ]) useEffect(() => { if (activeCardRef.current && type === HotelCardListingTypeEnum.MapListing) { activeCardRef.current.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center", }) } }, [activeHotel, type]) useEffect(() => { if (type === HotelCardListingTypeEnum.PageListing) { setResultCount(hotels.length, unfilteredHotelCount) } }, [hotels.length, setResultCount, type, unfilteredHotelCount]) function isHotelActiveInMapView(hotelName: string): boolean { return ( hotelName === activeHotel && type === HotelCardListingTypeEnum.MapListing ) } return (
{hotels.map((hotel) => (
({ ...redemption, localPrice: { ...redemption.localPrice, currency: redemption.localPrice.currency, }, }) ), } } onFocusIn={() => engage(hotel.hotel.name)} onFocusOut={() => disengage()} onHover={() => engage(hotel.hotel.name)} onHoverEnd={() => disengage()} onAddressClick={() => { const mapUrl = isAlternative ? alternativeHotelsMap(lang) : selectHotelMap(lang) disengage() // Disengage the current hotel to avoid the hover state from being active when clicking on the address activate(hotel.hotel.name) router.push(`${mapUrl}?${searchParams.toString()}`) }} belowInfoSlot={ } distanceToCityCenter={hotel.hotel.location.distanceToCentre} images={mapApiImagesToGalleryImages(hotel.hotel.galleryImages)} isUserLoggedIn={isUserLoggedIn} state={ isHotelActiveInMapView(hotel.hotel.name) ? "active" : "default" } type={type} bookingCode={bookingCode} isCampaignWithBookingCode={isCampaignWithBookingCode} isAlternative={isAlternative} isPartnerBrand={config.variant !== "scandic"} />
))} {showBackToTop && ( )}
) }