Merged in fix/BOOK-130-booking-code-filtering (pull request #2868)

fix(BOOK-130): update booking code filtering on map view and filter and sort modal

* fix(BOOK-130): update booking code filtering on map view and filter and sort modal

* fix(BOOK-130): change name of filteredIds

* fix(BOOK-130): add initial value reduce


Approved-by: Joakim Jäderberg
Approved-by: Anton Gunnarsson
This commit is contained in:
Bianca Widstam
2025-09-26 08:03:25 +00:00
parent b72f4c71e3
commit 7f3fd0c7a6
11 changed files with 63 additions and 63 deletions

View File

@@ -3,7 +3,6 @@
import { useCallback, useEffect, useRef } from "react" import { useCallback, useEffect, useRef } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { useHotelFilterStore } from "../../stores/hotel-filters"
import { useHotelsMapStore } from "../../stores/hotels-map" import { useHotelsMapStore } from "../../stores/hotels-map"
import ListingHotelCardDialog from "../ListingHotelCardDialog" import ListingHotelCardDialog from "../ListingHotelCardDialog"
import { getHotelPins } from "./utils" import { getHotelPins } from "./utils"
@@ -14,12 +13,10 @@ import type { HotelResponse } from "../SelectHotel/helpers"
interface HotelCardDialogListingProps { interface HotelCardDialogListingProps {
hotels: HotelResponse[] hotels: HotelResponse[]
unfilteredHotelCount: number
} }
export default function HotelCardDialogListing({ export default function HotelCardDialogListing({
hotels, hotels,
unfilteredHotelCount,
}: HotelCardDialogListingProps) { }: HotelCardDialogListingProps) {
const intl = useIntl() const intl = useIntl()
const isRedemption = hotels?.find( const isRedemption = hotels?.find(
@@ -37,7 +34,6 @@ export default function HotelCardDialogListing({
const isScrollingRef = useRef<boolean>(false) const isScrollingRef = useRef<boolean>(false)
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null) const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const { activeHotel, activate, deactivate } = useHotelsMapStore() const { activeHotel, activate, deactivate } = useHotelsMapStore()
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
const handleIntersection = useCallback( const handleIntersection = useCallback(
(entries: IntersectionObserverEntry[]) => { (entries: IntersectionObserverEntry[]) => {
@@ -125,10 +121,6 @@ export default function HotelCardDialogListing({
} }
}, [dialogRef, activeHotel, deactivate]) }, [dialogRef, activeHotel, deactivate])
useEffect(() => {
setResultCount(hotels.length, unfilteredHotelCount)
}, [hotels, setResultCount, unfilteredHotelCount])
return ( return (
<div className={styles.hotelCardDialogListing} ref={dialogRef}> <div className={styles.hotelCardDialogListing} ref={dialogRef}>
{hotelsPinData?.map((data) => { {hotelsPinData?.map((data) => {

View File

@@ -38,14 +38,12 @@ export enum HotelCardListingTypeEnum {
type HotelCardListingProps = { type HotelCardListingProps = {
hotelData: HotelResponse[] hotelData: HotelResponse[]
unfilteredHotelCount: number
type?: HotelCardListingTypeEnum type?: HotelCardListingTypeEnum
isAlternative?: boolean isAlternative?: boolean
} }
export default function HotelCardListing({ export default function HotelCardListing({
hotelData, hotelData,
unfilteredHotelCount,
type = HotelCardListingTypeEnum.PageListing, type = HotelCardListingTypeEnum.PageListing,
isAlternative, isAlternative,
}: HotelCardListingProps) { }: HotelCardListingProps) {
@@ -82,6 +80,10 @@ export default function HotelCardListing({
isBookingCodeRateAvailable && isBookingCodeRateAvailable &&
activeCodeFilter === BookingCodeFilterEnum.Discounted activeCodeFilter === BookingCodeFilterEnum.Discounted
const unfilteredHotelCount = showOnlyBookingCodeRates
? hotelData.filter((hotel) => hotel.availability.bookingCode).length
: hotelData.length
const hotels = useMemo(() => { const hotels = useMemo(() => {
const sortedHotels = getSortedHotels({ const sortedHotels = getSortedHotels({
hotels: hotelData, hotels: hotelData,
@@ -124,8 +126,10 @@ export default function HotelCardListing({
}, [activeHotel, type]) }, [activeHotel, type])
useEffect(() => { useEffect(() => {
setResultCount(hotels.length, unfilteredHotelCount) if (type === HotelCardListingTypeEnum.PageListing) {
}, [hotels, setResultCount, unfilteredHotelCount]) setResultCount(hotels.length, unfilteredHotelCount)
}
}, [hotels, setResultCount, type, unfilteredHotelCount])
function isHotelActiveInMapView(hotelName: string): boolean { function isHotelActiveInMapView(hotelName: string): boolean {
return ( return (

View File

@@ -227,9 +227,10 @@ export default function FilterAndSortModal({
defaultMessage: "See results ({ count })", defaultMessage: "See results ({ count })",
}, },
{ {
count: filteredCount count:
? filteredCount selectedFilters.length > 0
: unfilteredResultCount, ? filteredCount
: unfilteredResultCount,
} }
)} )}
</p> </p>

View File

@@ -43,25 +43,29 @@ export default function FilterContent({
const [filteredHotelIds, setFilteredHotelIds] = useState<string[]>([]) const [filteredHotelIds, setFilteredHotelIds] = useState<string[]>([])
useEffect(() => { useEffect(() => {
if (activeFilters.length) { const allFilters = [
const allFilters = [ ...filters.facilityFilters,
...filters.facilityFilters, ...filters.surroundingsFilters,
...filters.surroundingsFilters, ]
]
setFilteredHotelIds( const selectedFilters = activeFilters.length
allFilters ? allFilters
.filter((f) => activeFilters.includes(f.id.toString())) .filter((f) => activeFilters.includes(f.id.toString()))
.map((f) => f.hotelIds) .map((f) =>
.reduce((accumulatedHotelIds, currentHotelIds) => showOnlyBookingCodeRates ? f.bookingCodeFilteredIds : f.hotelIds
accumulatedHotelIds.filter((hotelId) =>
currentHotelIds.includes(hotelId)
)
) )
) : []
} else {
setFilteredHotelIds([]) const filteredIds = selectedFilters.reduce(
} (accumulatedHotelIds, currentHotelIds) =>
}, [filters, activeFilters, setFilteredHotelIds]) accumulatedHotelIds.filter((hotelId) =>
currentHotelIds.includes(hotelId)
),
selectedFilters[0] || []
)
setFilteredHotelIds(filteredIds)
}, [filters, activeFilters, setFilteredHotelIds, showOnlyBookingCodeRates])
useEffect(() => { useEffect(() => {
onFilteredCountChange(filteredHotelIds.length) onFilteredCountChange(filteredHotelIds.length)
@@ -74,7 +78,7 @@ export default function FilterContent({
function filterOutput(filters: HotelFilter[]) { function filterOutput(filters: HotelFilter[]) {
return filters.map((filter) => { return filters.map((filter) => {
const relevantIds = showOnlyBookingCodeRates const relevantIds = showOnlyBookingCodeRates
? filter.filteredIds ? filter.bookingCodeFilteredIds
: filter.hotelIds : filter.hotelIds
const isSelected = activeFilters.some((f) => f === filter.id.toString()) const isSelected = activeFilters.some((f) => f === filter.id.toString())

View File

@@ -14,29 +14,21 @@ import type { HotelResponse } from "../../helpers"
interface HotelListingProps { interface HotelListingProps {
hotels: HotelResponse[] hotels: HotelResponse[]
unfilteredHotelCount: number
} }
export default function HotelListing({ export default function HotelListing({ hotels }: HotelListingProps) {
hotels,
unfilteredHotelCount,
}: HotelListingProps) {
const { activeHotel } = useHotelsMapStore() const { activeHotel } = useHotelsMapStore()
const isMobile = useMediaQuery("(max-width: 899px)") const isMobile = useMediaQuery("(max-width: 899px)")
return isMobile ? ( return isMobile ? (
<div className={styles.hotelListingMobile} data-open={!!activeHotel}> <div className={styles.hotelListingMobile} data-open={!!activeHotel}>
<HotelCardDialogListing <HotelCardDialogListing hotels={hotels} />
hotels={hotels}
unfilteredHotelCount={unfilteredHotelCount}
/>
</div> </div>
) : ( ) : (
<div className={styles.hotelListing}> <div className={styles.hotelListing}>
<HotelCardListing <HotelCardListing
hotelData={hotels} hotelData={hotels}
type={HotelCardListingTypeEnum.MapListing} type={HotelCardListingTypeEnum.MapListing}
unfilteredHotelCount={unfilteredHotelCount}
/> />
</div> </div>
) )

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import { useMap } from "@vis.gl/react-google-maps" import { useMap } from "@vis.gl/react-google-maps"
import { useCallback, useMemo, useRef, useState } from "react" import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts" import { useMediaQuery } from "usehooks-ts"
@@ -75,6 +75,8 @@ export function SelectHotelMapContent({
const listingContainerRef = useRef<HTMLDivElement | null>(null) const listingContainerRef = useRef<HTMLDivElement | null>(null)
const activeFilters = useHotelFilterStore((state) => state.activeFilters) const activeFilters = useHotelFilterStore((state) => state.activeFilters)
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
const hotelMapStore = useHotelsMapStore() const hotelMapStore = useHotelsMapStore()
const { showBackToTop, scrollToTop } = useScrollToTop({ const { showBackToTop, scrollToTop } = useScrollToTop({
@@ -193,7 +195,13 @@ export function SelectHotelMapContent({
const showBookingCodeFilter = const showBookingCodeFilter =
bookingCode && isBookingCodeRateAvailable && !isSpecialRate bookingCode && isBookingCodeRateAvailable && !isSpecialRate
const unfilteredHotelCount = hotelPins.length const unfilteredHotelCount = showOnlyBookingCodeRates
? hotelPins.filter((hotel) => hotel.bookingCode).length
: hotelPins.length
useEffect(() => {
setResultCount(hotels.length, unfilteredHotelCount)
}, [hotels, setResultCount, unfilteredHotelCount])
return ( return (
<div className={styles.container}> <div className={styles.container}>
@@ -233,10 +241,7 @@ export function SelectHotelMapContent({
<RoomCardSkeleton /> <RoomCardSkeleton />
</div> </div>
) : ( ) : (
<HotelListing <HotelListing hotels={visibleHotels} />
hotels={visibleHotels}
unfilteredHotelCount={unfilteredHotelCount}
/>
)} )}
{showBackToTop && ( {showBackToTop && (
<BackToTopButton <BackToTopButton

View File

@@ -292,7 +292,8 @@ const hotelFacilitiesFilterNames = [
] ]
export function getFiltersFromHotels( export function getFiltersFromHotels(
hotels: HotelResponse[] hotels: HotelResponse[],
showBookingCodeFilter: boolean
): CategorizedHotelFilters { ): CategorizedHotelFilters {
const defaultFilters = { facilityFilters: [], surroundingsFilters: [] } const defaultFilters = { facilityFilters: [], surroundingsFilters: [] }
if (!hotels.length) { if (!hotels.length) {
@@ -306,7 +307,10 @@ export function getFiltersFromHotels(
...facility, ...facility,
hotelId: hotel.operaId, hotelId: hotel.operaId,
hotelIds: [hotel.operaId], hotelIds: [hotel.operaId],
filteredIds: availability.bookingCode ? [hotel.operaId] : [], bookingCodeFilteredIds:
availability.bookingCode || !showBookingCodeFilter
? [hotel.operaId]
: [],
} }
) )
) )
@@ -321,8 +325,10 @@ export function getFiltersFromHotels(
const matchingFilters = filters.filter((f) => f.id === filterId) const matchingFilters = filters.filter((f) => f.id === filterId)
filter.hotelIds = matchingFilters.map((f) => f.hotelId) filter.hotelIds = matchingFilters.map((f) => f.hotelId)
filter.filteredIds = [ filter.bookingCodeFilteredIds = [
...new Set(matchingFilters.flatMap((f) => f.filteredIds ?? [])), ...new Set(
matchingFilters.flatMap((f) => f.bookingCodeFilteredIds ?? [])
),
] ]
} }
return filter return filter

View File

@@ -57,10 +57,10 @@ export async function SelectHotel({
hotel.availability.productType?.voucher hotel.availability.productType?.voucher
) )
const filterList = getFiltersFromHotels(hotels)
const showBookingCodeFilter = isBookingCodeRateAvailable && !isSpecialRate const showBookingCodeFilter = isBookingCodeRateAvailable && !isSpecialRate
const filterList = getFiltersFromHotels(hotels, showBookingCodeFilter)
return ( return (
<> <>
<header className={styles.header}> <header className={styles.header}>
@@ -127,11 +127,7 @@ export async function SelectHotel({
bookingCode={bookingCode} bookingCode={bookingCode}
isBookingCodeRateNotAvailable={!isBookingCodeRateAvailable} isBookingCodeRateNotAvailable={!isBookingCodeRateAvailable}
/> />
<HotelCardListing <HotelCardListing hotelData={hotels} isAlternative={isAlternative} />
hotelData={hotels}
isAlternative={isAlternative}
unfilteredHotelCount={hotels.length}
/>
</div> </div>
</main> </main>
</> </>

View File

@@ -104,7 +104,7 @@ export async function AlternativeHotelsMapPage({
isRedemptionAvailable: isRedemptionAvailability, isRedemptionAvailable: isRedemptionAvailability,
}) })
const filterList = getFiltersFromHotels(hotels) const filterList = getFiltersFromHotels(hotels, isBookingCodeRateAvailable)
return ( return (
<MapContainer> <MapContainer>

View File

@@ -105,7 +105,7 @@ export async function SelectHotelMapPage({
isRedemptionAvailable: isRedemptionAvailability, isRedemptionAvailable: isRedemptionAvailability,
}) })
const filterList = getFiltersFromHotels(hotels) const filterList = getFiltersFromHotels(hotels, isBookingCodeRateAvailable)
const suspenseKey = stringify(searchParams) const suspenseKey = stringify(searchParams)

View File

@@ -5,7 +5,7 @@ export type NextSearchParams = { [key: string]: string | string[] | undefined }
export type HotelFilter = Hotel["detailedFacilities"][number] & { export type HotelFilter = Hotel["detailedFacilities"][number] & {
hotelId: string hotelId: string
hotelIds: string[] hotelIds: string[]
filteredIds: string[] bookingCodeFilteredIds: string[]
} }
export type CategorizedHotelFilters = { export type CategorizedHotelFilters = {