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

@@ -31,6 +31,7 @@ export default function RateSummary({
return () => clearTimeout(timer)
}, [])
if (rateSummary.length === 0) return null
const {
member,
public: publicRate,
@@ -38,7 +39,7 @@ export default function RateSummary({
roomType,
priceName,
priceTerm,
} = rateSummary
} = rateSummary[0] // TODO: Support multiple rooms
const isPetRoomSelected = features.some(
(feature) => feature.code === RoomPackageCodeEnum.PET_ROOM

View File

@@ -2,7 +2,7 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { useSearchParams } from "next/navigation"
import { useCallback, useEffect, useMemo, useState } from "react"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
@@ -26,26 +26,34 @@ export default function RoomFilter({
numberOfRooms,
onFilter,
filterOptions,
roomListIndex,
}: RoomFilterProps) {
const isTabletAndUp = useMediaQuery("(min-width: 768px)")
const [isAboveMobile, setIsAboveMobile] = useState(false)
const onFilterRef = useRef(onFilter)
const searchParams = useSearchParams()
const initialFilterValues = useMemo(() => {
const packagesFromSearchParams =
searchParams.get("room[0].packages")?.split(",") ?? []
searchParams.get(`room[${roomListIndex}].packages`)?.split(",") ?? []
const values = filterOptions.reduce(
return filterOptions.reduce(
(acc, option) => {
acc[option.code] = packagesFromSearchParams.includes(option.code)
return acc
},
{} as Record<string, boolean | undefined>
)
}, [filterOptions, searchParams, roomListIndex])
onFilter(values)
return values
}, [filterOptions, onFilter, searchParams])
useEffect(() => {
onFilterRef.current = onFilter
}, [onFilter])
useEffect(() => {
onFilterRef.current(initialFilterValues)
}, [initialFilterValues])
const intl = useIntl()
const methods = useForm<Record<string, boolean | undefined>>({
@@ -64,15 +72,19 @@ export default function RoomFilter({
const tooltipText = intl.formatMessage({
id: "Pet-friendly rooms have an additional fee of 20 EUR per stay",
})
const submitFilter = useCallback(() => {
const data = getValues()
onFilter(data)
}, [onFilter, getValues])
const submitFilter = useCallback(
(data: Record<string, boolean | undefined>) => {
onFilter(data)
},
[onFilter]
)
useEffect(() => {
const subscription = watch(() => handleSubmit(submitFilter)())
const subscription = watch((_, { name }) => {
if (name) submitFilter(getValues())
})
return () => subscription.unsubscribe()
}, [handleSubmit, watch, submitFilter])
}, [watch, submitFilter, getValues])
useEffect(() => {
setIsAboveMobile(isTabletAndUp)

View File

@@ -4,7 +4,7 @@ import { useSearchParams } from "next/navigation"
import { useEffect, useRef } from "react"
import { useIntl } from "react-intl"
import { CheckCircleIcon, CheckIcon, InfoCircleIcon } from "@/components/Icons"
import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button"
import Label from "@/components/TempDesignSystem/Form/Label"
@@ -25,9 +25,15 @@ export default function FlexibilityOption({
roomTypeCode,
petRoomPackage,
handleSelectRate,
roomListIndex,
}: FlexibilityOptionProps) {
const intl = useIntl()
const inputElementRef = useRef<HTMLInputElement>(null)
const handleSelectRateRef = useRef(handleSelectRate)
useEffect(() => {
handleSelectRateRef.current = handleSelectRate
}, [handleSelectRate])
const searchParams = useSearchParams()
@@ -35,8 +41,12 @@ export default function FlexibilityOption({
// want to preselect the selection. This happens e.g. when you do a selection,
// go to the enter details page and then want to change the room.
useEffect(() => {
const ratecodeSearchParam = searchParams.get("room[0].ratecode")
const roomtypeSearchParam = searchParams.get("room[0].roomtype")
const ratecodeSearchParam = searchParams.get(
`room[${roomListIndex}].ratecode`
)
const roomtypeSearchParam = searchParams.get(
`room[${roomListIndex}].roomtype`
)
// If this is not the room and rate we want to preselect, abort
if (
@@ -47,7 +57,7 @@ export default function FlexibilityOption({
return
}
handleSelectRate((prev) => {
handleSelectRateRef.current((prev) => {
// If the user already has made a new selection we respect that and don't do anything else
if (prev) {
return prev
@@ -64,7 +74,7 @@ export default function FlexibilityOption({
paymentTerm: paymentTerm,
}
})
}, [handleSelectRate, name, paymentTerm, product, roomTypeCode, searchParams])
}, [searchParams, roomListIndex, product, roomTypeCode, name, paymentTerm])
if (!product) {
return (
@@ -88,7 +98,7 @@ export default function FlexibilityOption({
const { public: publicPrice, member: memberPrice } = product.productType
const onClick: React.MouseEventHandler<HTMLInputElement> = (e) => {
handleSelectRate((prev) => {
handleSelectRateRef.current((prev) => {
if (
prev &&
prev.publicRateCode === publicPrice.rateCode &&
@@ -110,7 +120,7 @@ export default function FlexibilityOption({
<label>
<input
type="radio"
name="rateCode"
name={`rateCode-${roomListIndex}`}
value={publicPrice?.rateCode}
onClick={onClick}
ref={inputElementRef}

View File

@@ -30,6 +30,7 @@ export default function RoomCard({
selectedPackages,
packages,
handleSelectRate,
roomListIndex,
}: RoomCardProps) {
const intl = useIntl()
@@ -71,7 +72,7 @@ export default function RoomCard({
}
const petRoomPackage =
(selectedPackages.includes(RoomPackageCodeEnum.PET_ROOM) &&
(selectedPackages?.includes(RoomPackageCodeEnum.PET_ROOM) &&
packages?.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) ||
undefined
@@ -127,7 +128,7 @@ export default function RoomCard({
</span>
)}
{roomConfiguration.features
.filter((feature) => selectedPackages.includes(feature.code))
.filter((feature) => selectedPackages?.includes(feature.code))
.map((feature) => (
<span className={styles.chip} key={feature.code}>
{createElement(getIconForFeatureCode(feature.code), {
@@ -223,6 +224,7 @@ export default function RoomCard({
handleSelectRate={handleSelectRate}
roomTypeCode={roomConfiguration.roomTypeCode}
petRoomPackage={petRoomPackage}
roomListIndex={roomListIndex}
/>
))
)}

View File

@@ -13,6 +13,7 @@ export default function RoomList({
selectedPackages,
setRateCode,
hotelType,
roomListIndex,
}: RoomListProps) {
const { roomConfigurations, rateDefinitions } = roomsAvailability
@@ -30,6 +31,7 @@ export default function RoomList({
selectedPackages={selectedPackages}
packages={availablePackages}
key={roomConfiguration.roomTypeCode}
roomListIndex={roomListIndex}
/>
))}
</ul>

View File

@@ -12,6 +12,7 @@ export function RoomSelectionPanel({
hotelType,
handleFilter,
defaultPackages,
roomListIndex,
}: RoomSelectionPanelProps) {
return (
<>
@@ -19,6 +20,7 @@ export function RoomSelectionPanel({
numberOfRooms={rooms.roomConfigurations.length}
onFilter={handleFilter}
filterOptions={defaultPackages}
roomListIndex={roomListIndex}
/>
<RoomList
roomsAvailability={rooms}
@@ -27,6 +29,7 @@ export function RoomSelectionPanel({
selectedPackages={selectedPackages}
setRateCode={setSelectedRate}
hotelType={hotelType}
roomListIndex={roomListIndex}
/>
</>
)

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);
}

View File

@@ -0,0 +1,96 @@
import { useMemo } from "react"
import {
RoomPackageCodeEnum,
type RoomPackageData,
} from "@/types/components/hotelReservation/selectRate/roomFilter"
import type {
Rate,
RateCode,
} from "@/types/components/hotelReservation/selectRate/selectRate"
import type { RoomConfiguration } from "@/server/routers/hotels/output"
interface UseRateSummaryProps {
searchedRoomsAndGuests: Array<{ adults: number; children?: any[] }>
selectedRates: (RateCode | undefined)[]
getFilteredRooms: (roomIndex: number) => RoomConfiguration[]
selectedPackagesByRoom: Record<number, RoomPackageCodeEnum[]>
availablePackages: RoomPackageData
roomCategories: Array<{ name: string; roomTypes: Array<{ code: string }> }>
}
export function useRateSummary({
searchedRoomsAndGuests,
selectedRates,
getFilteredRooms,
selectedPackagesByRoom,
availablePackages,
roomCategories,
}: UseRateSummaryProps) {
return useMemo(() => {
const summaries: (Rate | null)[] = []
searchedRoomsAndGuests.forEach((_, roomIndex) => {
const selectedRate = selectedRates[roomIndex]
const filteredRooms = getFilteredRooms(roomIndex)
const selectedPackages = selectedPackagesByRoom[roomIndex] || []
const room = filteredRooms.find(
(room) => room.roomTypeCode === selectedRate?.roomTypeCode
)
if (!room) {
summaries[roomIndex] = null
return
}
const product = room.products.find(
(product) =>
product.productType.public.rateCode === selectedRate?.publicRateCode
)
if (!product) {
summaries[roomIndex] = null
return
}
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
)
)
summaries[roomIndex] = {
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 summaries
}, [
searchedRoomsAndGuests,
selectedRates,
getFilteredRooms,
selectedPackagesByRoom,
availablePackages,
roomCategories,
])
}

View File

@@ -0,0 +1,92 @@
import { useCallback, useMemo, useState } from "react"
import { filterDuplicateRoomTypesByLowestPrice } from "@/components/HotelReservation/SelectRate/Rooms/utils"
import type {
RoomPackageCodeEnum,
RoomPackageCodes,
} from "@/types/components/hotelReservation/selectRate/roomFilter"
import type {
RoomConfiguration,
RoomsAvailability,
} from "@/server/routers/hotels/output"
interface UseRoomFilteringProps {
roomsAvailability: RoomsAvailability
}
export function useRoomFiltering({ roomsAvailability }: UseRoomFilteringProps) {
const [selectedPackagesByRoom, setSelectedPackagesByRoom] = useState<
Record<number, RoomPackageCodes[]>
>({})
const visibleRooms = useMemo(() => {
const deduped = filterDuplicateRoomTypesByLowestPrice(
roomsAvailability.roomConfigurations
)
const separated = deduped.reduce<{
available: RoomConfiguration[]
notAvailable: RoomConfiguration[]
}>(
(acc, curr) => {
if (curr.status === "NotAvailable")
return { ...acc, notAvailable: [...acc.notAvailable, curr] }
return { ...acc, available: [...acc.available, curr] }
},
{ available: [], notAvailable: [] }
)
return [...separated.available, ...separated.notAvailable]
}, [roomsAvailability.roomConfigurations])
const handleFilter = useCallback(
(
filter: Record<RoomPackageCodeEnum, boolean | undefined>,
roomIndex: number
) => {
const filteredPackages = Object.keys(filter).filter(
(key) => filter[key as RoomPackageCodeEnum]
) as RoomPackageCodeEnum[]
setSelectedPackagesByRoom((prev) => ({
...prev,
[roomIndex]: filteredPackages,
}))
},
[]
)
const getFilteredRooms = useCallback(
(roomIndex: number) => {
const selectedPackages = selectedPackagesByRoom[roomIndex] || []
return visibleRooms.filter((room) =>
selectedPackages.every((filteredPackage) =>
room.features.some((feature) => feature.code === filteredPackage)
)
)
},
[visibleRooms, selectedPackagesByRoom]
)
const getRooms = useCallback(
(roomIndex: number) => {
const selectedPackages = selectedPackagesByRoom[roomIndex] || []
const filteredRooms = getFilteredRooms(roomIndex)
return {
...roomsAvailability,
roomConfigurations:
selectedPackages.length === 0 ? visibleRooms : filteredRooms,
}
},
[roomsAvailability, visibleRooms, selectedPackagesByRoom, getFilteredRooms]
)
return {
selectedPackagesByRoom,
getFilteredRooms,
getRooms,
handleFilter,
}
}

View File

@@ -21,6 +21,7 @@ export type FlexibilityOptionProps = {
roomTypeCode: RoomConfiguration["roomTypeCode"]
petRoomPackage: RoomPackage | undefined
handleSelectRate: React.Dispatch<React.SetStateAction<RateCode | undefined>>
roomListIndex: number
}
export interface PriceListProps {

View File

@@ -3,7 +3,7 @@ import type { RoomPackageData } from "./roomFilter"
import type { Rate } from "./selectRate"
export interface RateSummaryProps {
rateSummary: Rate
rateSummary: Rate[]
isUserLoggedIn: boolean
packages: RoomPackageData | undefined
roomsAvailability: RoomsAvailability

View File

@@ -19,6 +19,7 @@ export type RoomCardProps = {
selectedPackages: RoomPackageCodes[]
packages: RoomPackageData | undefined
handleSelectRate: React.Dispatch<React.SetStateAction<RateCode | undefined>>
roomListIndex: number
}
type RoomPackagePriceSchema = z.output<typeof packagePriceSchema>

View File

@@ -1,6 +1,6 @@
import { z } from "zod"
import type { z } from "zod"
import { packagesSchema } from "@/server/routers/hotels/output"
import type { packagesSchema } from "@/server/routers/hotels/output"
export enum RoomPackageCodeEnum {
PET_ROOM = "PETR",
@@ -17,6 +17,7 @@ export interface RoomFilterProps {
numberOfRooms: number
onFilter: (filter: Record<string, boolean | undefined>) => void
filterOptions: DefaultFilterOptions[]
roomListIndex: number
}
export type RoomPackage = z.output<typeof packagesSchema>

View File

@@ -16,6 +16,7 @@ export interface RoomListProps {
selectedPackages: RoomPackageCodes[]
setRateCode: React.Dispatch<React.SetStateAction<RateCode | undefined>>
hotelType: string | undefined
roomListIndex: number
}
export interface SelectRateProps {
@@ -37,4 +38,5 @@ export interface RoomSelectionPanelProps {
filter: Record<RoomPackageCodeEnum, boolean | undefined>
) => void
defaultPackages: DefaultFilterOptions[]
roomListIndex: number
}

View File

@@ -32,6 +32,7 @@ export interface Rate {
public: Product["productType"]["public"]
member?: Product["productType"]["member"]
features: RoomConfiguration["features"]
roomRates?: Array<{ roomIndex: number; rate: Rate }>
}
export type RateCode = {