feat(SW-718) Fixed filtering with multirooms

This commit is contained in:
Pontus Dreij
2025-01-21 14:40:39 +01:00
parent edcf146ce1
commit 328cbbe0e1
16 changed files with 326 additions and 151 deletions

View File

@@ -5,6 +5,8 @@ import { useCallback, useEffect, useMemo, useState } from "react"
import { useIntl } from "react-intl"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { useRateSummary } from "@/hooks/selectRate/useRateSummary"
import { useRoomFiltering } from "@/hooks/selectRate/useRoomFiltering"
import { trackLowestRoomPrice } from "@/utils/tracking"
import { convertObjToSearchParams } from "@/utils/url"
@@ -17,7 +19,6 @@ import styles from "./rooms.module.css"
import {
type DefaultFilterOptions,
RoomPackageCodeEnum,
type RoomPackageCodes,
} from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { SelectRateProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
import type {
@@ -26,6 +27,8 @@ import type {
} from "@/types/components/hotelReservation/selectRate/selectRate"
import type { RoomConfiguration } from "@/server/routers/hotels/output"
type SelectedRates = (RateCode | undefined)[]
export default function Rooms({
roomsAvailability,
roomCategories = [],
@@ -46,6 +49,10 @@ export default function Rooms({
[searchParams]
)
const [selectedRates, setSelectedRates] = useState<SelectedRates>(
new Array(searchedRoomsAndGuests.length).fill(undefined)
)
const isMultipleRooms = searchedRoomsAndGuests.length > 1
const intl = useIntl()
@@ -71,13 +78,6 @@ export default function Rooms({
return [...separated.available, ...separated.notAvailable]
}, [roomsAvailability.roomConfigurations])
const [selectedRate, setSelectedRate] = useState<RateCode | undefined>(
undefined
)
const [selectedPackages, setSelectedPackages] = useState<RoomPackageCodes[]>(
[]
)
const defaultPackages: DefaultFilterOptions[] = useMemo(
() => [
{
@@ -105,142 +105,60 @@ export default function Rooms({
[availablePackages, intl]
)
const handleFilter = useCallback(
(filter: Record<RoomPackageCodeEnum, boolean | undefined>) => {
const filteredPackages = Object.keys(filter).filter(
(key) => filter[key as RoomPackageCodeEnum]
) as RoomPackageCodeEnum[]
const { selectedPackagesByRoom, getRooms, handleFilter, getFilteredRooms } =
useRoomFiltering({ roomsAvailability })
setSelectedPackages(filteredPackages)
},
[]
)
const filteredRooms = useMemo(() => {
return visibleRooms.filter((room) =>
selectedPackages.every((filteredPackage) =>
room.features.some((feature) => feature.code === filteredPackage)
)
)
}, [visibleRooms, selectedPackages])
const rooms = useMemo(() => {
if (selectedPackages.length === 0) {
return {
...roomsAvailability,
roomConfigurations: visibleRooms,
}
}
return {
...roomsAvailability,
roomConfigurations: [...filteredRooms],
}
}, [roomsAvailability, visibleRooms, selectedPackages, filteredRooms])
const rateSummary: Rate | null = useMemo(() => {
const room = filteredRooms.find(
(room) => room.roomTypeCode === selectedRate?.roomTypeCode
)
if (!room) return null
const product = room.products.find(
(product) =>
product.productType.public.rateCode === selectedRate?.publicRateCode
)
if (!product) return null
const petRoomPackage =
(selectedPackages.includes(RoomPackageCodeEnum.PET_ROOM) &&
availablePackages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
)) ||
undefined
const features = filteredRooms.find((room) =>
room.features.some(
(feature) => feature.code === RoomPackageCodeEnum.PET_ROOM
)
)?.features
const roomType = roomCategories.find((roomCategory) =>
roomCategory.roomTypes.some(
(roomType) => roomType.code === room.roomTypeCode
)
)
const rateSummary: Rate = {
features: petRoomPackage && features ? features : [],
priceName: selectedRate?.name,
priceTerm: selectedRate?.paymentTerm,
public: product.productType.public,
member: product.productType.member,
roomType: roomType?.name || room.roomType,
roomTypeCode: room.roomTypeCode,
}
return rateSummary
}, [
filteredRooms,
const rateSummary = useRateSummary({
searchedRoomsAndGuests,
selectedRates,
getFilteredRooms,
selectedPackagesByRoom,
availablePackages,
selectedPackages,
selectedRate,
roomCategories,
])
})
useEffect(() => {
if (rateSummary) return
if (!selectedRate) return
if (!rateSummary?.some((rate) => rate === null)) return
setSelectedRate(undefined)
}, [rateSummary, selectedRate])
const hasAnySelection = selectedRates.some((rate) => rate !== undefined)
if (!hasAnySelection) return
}, [rateSummary, selectedRates])
useEffect(() => {
const pricesWithCurrencies = rooms.roomConfigurations.flatMap((room) =>
const pricesWithCurrencies = visibleRooms.flatMap((room) =>
room.products.map((product) => ({
price: product.productType.public.localPrice.pricePerNight,
currency: product.productType.public.localPrice.currency,
}))
)
const cheapestPrice = pricesWithCurrencies.reduce(
const lowestPrice = pricesWithCurrencies.reduce(
(minPrice, { price }) => Math.min(minPrice, price),
Infinity
)
const currency = pricesWithCurrencies.find(
({ price }) => price === cheapestPrice
)?.currency
const currency = pricesWithCurrencies[0]?.currency
trackLowestRoomPrice({
hotelId,
arrivalDate,
departureDate,
lowestPrice: cheapestPrice,
lowestPrice: lowestPrice,
currency: currency,
})
}, [arrivalDate, departureDate, hotelId, rooms.roomConfigurations])
}, [arrivalDate, departureDate, hotelId, visibleRooms])
const queryParams = useMemo(() => {
// TODO: handle multiple rooms
const newSearchParams = convertObjToSearchParams(
{
rooms: [
{
roomTypeCode: rateSummary?.roomTypeCode,
rateCode: rateSummary?.public.rateCode,
counterRateCode: rateSummary?.member?.rateCode,
packages: selectedPackages,
},
],
},
searchParams
)
const rooms = rateSummary.map((rate, index) => ({
roomTypeCode: rate?.roomTypeCode,
rateCode: rate?.public.rateCode,
counterRateCode: rate?.member?.rateCode,
packages: selectedPackagesByRoom[index] || [],
}))
const newSearchParams = convertObjToSearchParams({ rooms }, searchParams)
return newSearchParams
}, [searchParams, rateSummary, selectedPackages])
}, [searchParams, rateSummary, selectedPackagesByRoom])
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
@@ -253,11 +171,31 @@ export default function Rooms({
router.push(`select-bed?${queryParams}`)
}
const setSelectedRateForRoom = useCallback(
(index: number) => (value: React.SetStateAction<RateCode | undefined>) => {
setSelectedRates((prev) => {
const newRates = [...prev]
newRates[index] =
typeof value === "function" ? value(prev[index]) : value
return newRates
})
},
[]
)
const handleFilterForRoom = useCallback(
(index: number) =>
(filter: Record<RoomPackageCodeEnum, boolean | undefined>) => {
handleFilter(filter, index)
},
[handleFilter]
)
return (
<div className={styles.content}>
{isMultipleRooms ? (
searchedRoomsAndGuests.map((room, index) => (
<div key={index}>
<div key={index} className={styles.roomContainer}>
<Subtitle>
{intl.formatMessage(
{
@@ -273,27 +211,29 @@ export default function Rooms({
)}
</Subtitle>
<RoomSelectionPanel
rooms={rooms}
rooms={getRooms(index)}
roomCategories={roomCategories}
availablePackages={availablePackages}
selectedPackages={selectedPackages}
setSelectedRate={setSelectedRate}
selectedPackages={selectedPackagesByRoom[index]}
setSelectedRate={setSelectedRateForRoom(index)}
hotelType={hotelType}
handleFilter={handleFilter}
handleFilter={handleFilterForRoom(index)}
defaultPackages={defaultPackages}
roomListIndex={index}
/>
</div>
))
) : (
<RoomSelectionPanel
rooms={rooms}
rooms={getRooms(0)}
roomCategories={roomCategories}
availablePackages={availablePackages}
selectedPackages={selectedPackages}
setSelectedRate={setSelectedRate}
selectedPackages={selectedPackagesByRoom[0]}
setSelectedRate={setSelectedRateForRoom(0)}
hotelType={hotelType}
handleFilter={handleFilter}
handleFilter={handleFilterForRoom(0)}
defaultPackages={defaultPackages}
roomListIndex={0}
/>
)}
@@ -304,7 +244,9 @@ export default function Rooms({
onSubmit={handleSubmit}
>
<RateSummary
rateSummary={rateSummary}
rateSummary={rateSummary.filter(
(summary): summary is Rate => summary !== null
)}
isUserLoggedIn={isUserLoggedIn}
packages={availablePackages}
roomsAvailability={roomsAvailability}

View File

@@ -6,3 +6,12 @@
gap: var(--Spacing-x2);
padding: var(--Spacing-x2) 0;
}
.roomContainer {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
border: 1px solid var(--Base-Border-Subtle);
border-radius: var(--Corner-radius-Large);
padding: var(--Spacing-x2) var(--Spacing-x2) 0 var(--Spacing-x2);
}