feat(SW-718) updates after PR comments

This commit is contained in:
Pontus Dreij
2025-01-27 17:08:57 +01:00
parent bfdc62d263
commit 68d7e869db
29 changed files with 371 additions and 321 deletions

View File

@@ -1,6 +1,6 @@
import SkeletonShimmer from "@/components/SkeletonShimmer"
import { RoomCardSkeleton } from "../../SelectRate/RoomList/RoomCard/RoomCardSkeleton"
import { RoomCardSkeleton } from "../RoomCardSkeleton/RoomCardSkeleton"
import styles from "./SelectHotelMapContainerSkeleton.module.css"

View File

@@ -8,7 +8,7 @@ import { selectHotel } from "@/constants/routes/hotelReservation"
import { useHotelFilterStore } from "@/stores/hotel-filters"
import { useHotelsMapStore } from "@/stores/hotels-map"
import { RoomCardSkeleton } from "@/components/HotelReservation/SelectRate/RoomList/RoomCard/RoomCardSkeleton"
import { RoomCardSkeleton } from "@/components/HotelReservation/SelectHotel/RoomCardSkeleton/RoomCardSkeleton"
import { CloseIcon, CloseLargeIcon } from "@/components/Icons"
import InteractiveMap from "@/components/Maps/InteractiveMap"
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from "react"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import { useRateSelectionStore } from "@/stores/rate-selection"
import { useRateSelectionStore } from "@/stores/select-rate/rate-selection"
import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop"
import SignupPromoMobile from "@/components/HotelReservation/SignupPromo/Mobile"
@@ -34,12 +34,12 @@ export default function RateSummary({
return () => clearTimeout(timer)
}, [])
if (rateSummary.length === 0) return null
const selectedRateSummary = rateSummary.filter(
(summary): summary is Rate => summary !== null
)
if (selectedRateSummary.length === 0) return null
const {
member,
public: publicRate,

View File

@@ -1,6 +1,10 @@
import RoomFilter from "../RoomFilter"
import RoomList from "../RoomList"
import { useSearchParams } from "next/navigation"
import { useMemo } from "react"
import RoomTypeFilter from "../RoomTypeFilter"
import RoomTypeList from "../RoomTypeList"
import type { FilterValues } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { RoomSelectionPanelProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
export function RoomSelectionPanel({
@@ -13,15 +17,27 @@ export function RoomSelectionPanel({
defaultPackages,
roomListIndex,
}: RoomSelectionPanelProps) {
const searchParams = useSearchParams()
const initialFilterValues = useMemo(() => {
const packagesFromSearchParams =
searchParams.get(`room[${roomListIndex}].packages`)?.split(",") ?? []
return defaultPackages.reduce<FilterValues>((acc, option) => {
acc[option.code] = packagesFromSearchParams.includes(option.code)
return acc
}, {})
}, [defaultPackages, searchParams, roomListIndex])
return (
<>
<RoomFilter
<RoomTypeFilter
numberOfRooms={rooms.roomConfigurations.length}
onFilter={handleFilter}
filterOptions={defaultPackages}
roomListIndex={roomListIndex}
initialFilterValues={initialFilterValues}
/>
<RoomList
<RoomTypeList
roomsAvailability={rooms}
roomCategories={roomCategories}
availablePackages={availablePackages}

View File

@@ -1,7 +1,6 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useSearchParams } from "next/navigation"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
@@ -27,24 +26,12 @@ export default function RoomFilter({
numberOfRooms,
onFilter,
filterOptions,
roomListIndex,
initialFilterValues,
}: 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[${roomListIndex}].packages`)?.split(",") ?? []
return filterOptions.reduce<FilterValues>((acc, option) => {
acc[option.code] = packagesFromSearchParams.includes(option.code)
return acc
}, {})
}, [filterOptions, searchParams, roomListIndex])
useEffect(() => {
onFilterRef.current = onFilter
}, [onFilter])

View File

@@ -1,11 +1,7 @@
"use client"
import { useSearchParams } from "next/navigation"
import { useEffect, useRef } from "react"
import { useIntl } from "react-intl"
import { useRateSelectionStore } from "@/stores/rate-selection"
import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button"
@@ -24,60 +20,11 @@ export default function FlexibilityOption({
name,
paymentTerm,
priceInformation,
roomTypeCode,
petRoomPackage,
roomListIndex,
isSelected,
onSelect,
}: FlexibilityOptionProps) {
const intl = useIntl()
const inputElementRef = useRef<HTMLInputElement>(null)
const { selectRate, selectedRates } = useRateSelectionStore()
const searchParams = useSearchParams()
// When entering the page with a room and rate selection already in the URL we
// 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[${roomListIndex}].ratecode`
)
const roomtypeSearchParam = searchParams.get(
`room[${roomListIndex}].roomtype`
)
// If this is not the room and rate we want to preselect, abort
if (
!product ||
ratecodeSearchParam !== product.productType.public.rateCode ||
roomtypeSearchParam !== roomTypeCode
) {
return
}
// Check if there's already a selection for this room index
const existingSelection = selectedRates[roomListIndex]
if (existingSelection) return
selectRate(roomListIndex, {
publicRateCode: product.productType.public.rateCode,
roomTypeCode: roomTypeCode,
name: name,
paymentTerm: paymentTerm,
})
if (inputElementRef.current) {
inputElementRef.current.checked = true
}
}, [
searchParams,
roomListIndex,
product,
roomTypeCode,
name,
paymentTerm,
selectedRates,
selectRate,
])
if (!product) {
return (
@@ -100,31 +47,14 @@ export default function FlexibilityOption({
const { public: publicPrice, member: memberPrice } = product.productType
const onClick: React.MouseEventHandler<HTMLInputElement> = (e) => {
if (
selectedRates[roomListIndex]?.publicRateCode === publicPrice.rateCode &&
selectedRates[roomListIndex]?.roomTypeCode === roomTypeCode
) {
if (e.currentTarget?.checked) e.currentTarget.checked = false
selectRate(roomListIndex, undefined)
} else {
selectRate(roomListIndex, {
publicRateCode: publicPrice.rateCode,
roomTypeCode: roomTypeCode,
name: name,
paymentTerm: paymentTerm,
})
}
}
return (
<label>
<input
type="radio"
name={`rateCode-${roomListIndex}`}
name={`rateCode-${product.productType.public.rateCode}`}
value={publicPrice?.rateCode}
onClick={onClick}
ref={inputElementRef}
checked={isSelected}
onChange={onSelect}
/>
<div className={styles.card}>
<div className={styles.header}>

View File

@@ -1,9 +1,10 @@
"use client"
import { createElement, useCallback } from "react"
import { useSearchParams } from "next/navigation"
import { createElement, useCallback, useEffect, useMemo } from "react"
import { useIntl } from "react-intl"
import { useRateSelectionStore } from "@/stores/rate-selection"
import { useRateSelectionStore } from "@/stores/select-rate/rate-selection"
import ToggleSidePeek from "@/components/HotelReservation/EnterDetails/SelectedRoom/ToggleSidePeek"
import { getIconForFeatureCode } from "@/components/HotelReservation/utils"
@@ -34,22 +35,27 @@ export default function RoomCard({
roomListIndex,
}: RoomCardProps) {
const intl = useIntl()
const searchParams = useSearchParams()
const { selectRate, selectedRates } = useRateSelectionStore()
const selectedRate = useRateSelectionStore(
(state) => state.selectedRates[roomListIndex]
)
const rates = {
saveRate: rateDefinitions.find(
(rate) => rate.cancellationRule === "NotCancellable"
),
changeRate: rateDefinitions.find(
(rate) => rate.cancellationRule === "Changeable"
),
flexRate: rateDefinitions.find(
(rate) => rate.cancellationRule === "CancellableBefore6PM"
),
}
const rates = useMemo(
() => ({
saveRate: rateDefinitions.find(
(rate) => rate.cancellationRule === "NotCancellable"
),
changeRate: rateDefinitions.find(
(rate) => rate.cancellationRule === "Changeable"
),
flexRate: rateDefinitions.find(
(rate) => rate.cancellationRule === "CancellableBefore6PM"
),
}),
[rateDefinitions]
)
function findProductForRate(rate: RateDefinition | undefined) {
return rate
@@ -116,6 +122,69 @@ export default function RoomCard({
: "default",
})
// Handle URL-based preselection
useEffect(() => {
const ratecodeSearchParam = searchParams.get(
`room[${roomListIndex}].ratecode`
)
const roomtypeSearchParam = searchParams.get(
`room[${roomListIndex}].roomtype`
)
if (!ratecodeSearchParam || !roomtypeSearchParam) return
// Check if there's already a selection for this room index
const existingSelection = selectedRates[roomListIndex]
if (existingSelection) return
const matchingRate = Object.entries(rates).find(
([_, rate]) =>
rate?.rateCode === ratecodeSearchParam &&
roomConfiguration.roomTypeCode === roomtypeSearchParam
)
if (matchingRate) {
const [key, rate] = matchingRate
selectRate(roomListIndex, {
publicRateCode: rate?.rateCode ?? "",
roomTypeCode: roomConfiguration.roomTypeCode,
name: rateKey(key),
paymentTerm: key === "flexRate" ? payLater : payNow,
})
}
}, [
searchParams,
roomListIndex,
rates,
roomConfiguration.roomTypeCode,
payLater,
payNow,
selectRate,
selectedRates,
rateKey,
])
const handleRateSelection = (
rateCode: string,
rateName: string,
paymentTerm: string
) => {
if (
selectedRates[roomListIndex]?.publicRateCode === rateCode &&
selectedRates[roomListIndex]?.roomTypeCode ===
roomConfiguration.roomTypeCode
) {
selectRate(roomListIndex, undefined)
} else {
selectRate(roomListIndex, {
publicRateCode: rateCode,
roomTypeCode: roomConfiguration.roomTypeCode,
name: rateName,
paymentTerm: paymentTerm,
})
}
}
return (
<li className={classNames}>
<div>
@@ -220,7 +289,7 @@ export default function RoomCard({
) : (
Object.entries(rates).map(([key, rate]) => (
<FlexibilityOption
key={`${roomListIndex}-${rate?.rateCode}-${selectedRate?.roomTypeCode || "unselected"}`}
key={`${roomListIndex}-${rate?.rateCode ?? key}-${selectedRate?.roomTypeCode ?? "unselected"}`}
name={rateKey(key)}
value={key.toLowerCase()}
paymentTerm={key === "flexRate" ? payLater : payNow}
@@ -228,7 +297,17 @@ export default function RoomCard({
priceInformation={getRateDefinitionForRate(rate)?.generalTerms}
roomTypeCode={roomConfiguration.roomTypeCode}
petRoomPackage={petRoomPackage}
roomListIndex={roomListIndex}
isSelected={
selectedRate?.publicRateCode === rate?.rateCode &&
selectedRate?.roomTypeCode === roomConfiguration.roomTypeCode
}
onSelect={() =>
handleRateSelection(
rate?.rateCode ?? "",
rateKey(key),
key === "flexRate" ? payLater : payNow
)
}
/>
))
)}

View File

@@ -4,16 +4,16 @@ import RoomCard from "./RoomCard"
import styles from "./roomSelection.module.css"
import type { RoomListProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
import type { RoomTypeListProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
export default function RoomList({
export default function RoomTypeList({
roomsAvailability,
roomCategories,
availablePackages,
selectedPackages,
hotelType,
roomListIndex,
}: RoomListProps) {
}: RoomTypeListProps) {
const { roomConfigurations, rateDefinitions } = roomsAvailability
return (

View File

@@ -1,4 +1,4 @@
import { RoomCardSkeleton } from "../RoomList/RoomCard/RoomCardSkeleton"
import { RoomCardSkeleton } from "../../SelectHotel/RoomCardSkeleton/RoomCardSkeleton"
import styles from "./RoomsContainerSkeleton.module.css"

View File

@@ -4,7 +4,7 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useCallback, useEffect, useMemo } from "react"
import { useIntl } from "react-intl"
import { useRateSelectionStore } from "@/stores/rate-selection"
import { useRateSelectionStore } from "@/stores/select-rate/rate-selection"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { useRoomFiltering } from "@/hooks/selectRate/useRoomFiltering"
@@ -15,6 +15,7 @@ import RateSummary from "../RateSummary"
import { RoomSelectionPanel } from "../RoomSelectionPanel"
import SelectedRoomPanel from "../SelectedRoomPanel"
import { filterDuplicateRoomTypesByLowestPrice } from "./utils"
import { roomSelectionPanelVariants } from "./variants"
import styles from "./rooms.module.css"
@@ -41,13 +42,8 @@ export default function Rooms({
const arrivalDate = searchParams.get("fromDate")
const departureDate = searchParams.get("toDate")
const {
modifyRate,
selectedRates,
rateSummary,
calculateRateSummary,
initializeRates,
} = useRateSelectionStore()
const { selectedRates, rateSummary, calculateRateSummary, initializeRates } =
useRateSelectionStore()
const bookingWidgetSearchData = useMemo(
() =>
@@ -117,13 +113,19 @@ export default function Rooms({
useRoomFiltering({ roomsAvailability })
useEffect(() => {
calculateRateSummary({
getFilteredRooms,
availablePackages,
roomCategories,
selectedPackagesByRoom,
})
if (
selectedRates.length > 0 &&
selectedRates.some((rate) => rate !== undefined)
) {
calculateRateSummary({
getFilteredRooms,
availablePackages,
roomCategories,
selectedPackagesByRoom,
})
}
}, [
selectedRates,
getFilteredRooms,
availablePackages,
roomCategories,
@@ -177,11 +179,7 @@ export default function Rooms({
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
window.history.replaceState(
null,
"",
`${pathname}?${queryParams.toString()}`
)
window.history.pushState(null, "", `${pathname}?${queryParams.toString()}`)
router.push(`select-bed?${queryParams}`)
}
@@ -215,58 +213,66 @@ export default function Rooms({
return (
<div className={styles.content}>
{isMultipleRooms ? (
bookingWidgetSearchData.rooms.map((room, index) => (
<div key={index} className={styles.roomContainer}>
{selectedRates[index] === undefined && (
<Subtitle>
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: index + 1 }
)}
,{" "}
{intl.formatMessage(
{
id: room.childrenInRoom?.length
? "{adults} adults, {children} children"
: "{adults} adults",
},
{
adults: room.adults,
children: room.childrenInRoom?.length,
}
)}
</Subtitle>
)}
<div
className={styles.roomSelectionPanelContainer}
data-selected={selectedRates[index] !== undefined}
data-active-panel={
(index === 0 || selectedRates[index - 1] !== undefined) &&
selectedRates[index] === undefined
}
>
<div className={styles.selectedRoomPanel}>
<SelectedRoomPanel
roomIndex={index}
room={room}
roomCategories={roomCategories}
/>
</div>
<div className={styles.roomSelectionPanel}>
<RoomSelectionPanel
rooms={getRooms(index)}
roomCategories={roomCategories}
availablePackages={availablePackages}
selectedPackages={selectedPackagesByRoom[index]}
hotelType={hotelType}
handleFilter={handleFilterForRoom(index)}
defaultPackages={defaultPackages}
roomListIndex={index}
/>
bookingWidgetSearchData.rooms.map((room, index) => {
const classNames = roomSelectionPanelVariants({
active:
(index === 0 || selectedRates[index - 1] !== undefined) &&
selectedRates[index] === undefined,
selected: selectedRates[index] !== undefined,
})
return (
<div key={index} className={styles.roomContainer}>
{selectedRates[index] === undefined && (
<Subtitle>
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: index + 1 }
)}
,{" "}
{intl.formatMessage(
{
id: room.childrenInRoom?.length
? "{adults} adults, {children} children"
: "{adults} adults",
},
{
adults: room.adults,
children: room.childrenInRoom?.length,
}
)}
</Subtitle>
)}
<div
className={classNames}
data-selected={selectedRates[index] !== undefined}
data-active-panel={
(index === 0 || selectedRates[index - 1] !== undefined) &&
selectedRates[index] === undefined
}
>
<div className={styles.selectedRoomPanel}>
<SelectedRoomPanel
roomIndex={index}
room={room}
roomCategories={roomCategories}
/>
</div>
<div className={styles.roomSelectionPanel}>
<RoomSelectionPanel
rooms={getRooms(index)}
roomCategories={roomCategories}
availablePackages={availablePackages}
selectedPackages={selectedPackagesByRoom[index]}
hotelType={hotelType}
handleFilter={handleFilterForRoom(index)}
defaultPackages={defaultPackages}
roomListIndex={index}
/>
</div>
</div>
</div>
</div>
))
)
})
) : (
<RoomSelectionPanel
rooms={getRooms(0)}

View File

@@ -15,45 +15,39 @@
padding: var(--Spacing-x2);
}
.roomSelectionPanel {
._basePanel {
display: grid;
grid-template-rows: 0fr;
opacity: 0;
transition:
opacity 0.3s ease,
grid-template-rows 0.5s ease;
height: 0;
gap: var(--Spacing-x2);
}
.roomSelectionPanel > * {
overflow: hidden;
}
.selectedRoomPanel {
display: grid;
grid-template-rows: 0fr;
opacity: 0;
transition:
opacity 0.3s ease,
grid-template-rows 0.3s ease;
height: 0;
}
.selectedRoomPanel > * {
._basePanel > * {
overflow: hidden;
}
.roomSelectionPanelContainer[data-selected="true"] .selectedRoomPanel {
.roomSelectionPanel {
composes: _basePanel;
gap: var(--Spacing-x2);
}
.selectedRoomPanel {
composes: _basePanel;
}
.roomSelectionPanelContainer.selected .selectedRoomPanel {
grid-template-rows: 1fr;
opacity: 1;
height: auto;
}
.roomSelectionPanelContainer[data-selected="true"] .roomSelectionPanel {
.roomSelectionPanelContainer.selected .roomSelectionPanel {
display: none;
}
.roomSelectionPanelContainer[data-active-panel="true"] .roomSelectionPanel {
.roomSelectionPanelContainer.active .roomSelectionPanel {
grid-template-rows: 1fr;
opacity: 1;
height: auto;

View File

@@ -0,0 +1,21 @@
import { cva } from "class-variance-authority"
import styles from "./rooms.module.css"
export const roomSelectionPanelVariants = cva(
styles.roomSelectionPanelContainer,
{
variants: {
active: {
true: styles.active,
},
selected: {
true: styles.selected,
},
},
defaultVariants: {
active: false,
selected: false,
},
}
)

View File

@@ -2,7 +2,7 @@
import { useCallback } from "react"
import { useIntl } from "react-intl"
import { useRateSelectionStore } from "@/stores/rate-selection"
import { useRateSelectionStore } from "@/stores/select-rate/rate-selection"
import { EditIcon } from "@/components/Icons"
import Image from "@/components/Image"
@@ -28,6 +28,7 @@ export default function SelectedRoomPanel({
}: SelectedRoomPanelProps) {
const intl = useIntl()
const { rateSummary, modifyRate } = useRateSelectionStore()
const selectedRate = rateSummary[roomIndex]
const images = roomCategories.find((roomCategory) =>
roomCategory.roomTypes.some(

View File

@@ -1,110 +0,0 @@
import { create } from "zustand"
import type {
RoomPackageCodeEnum,
RoomPackageData} from "@/types/components/hotelReservation/selectRate/roomFilter";
import type { RoomParam } from "@/types/components/hotelReservation/selectRate/section"
import type {
Rate,
RateCode,
} from "@/types/components/hotelReservation/selectRate/selectRate"
import type { RoomConfiguration } from "@/server/routers/hotels/output"
interface RateSummaryParams {
getFilteredRooms: (roomIndex: number) => RoomConfiguration[]
availablePackages: RoomPackageData
roomCategories: Array<{ name: string; roomTypes: Array<{ code: string }> }>
selectedPackagesByRoom: Record<number, RoomPackageCodeEnum[]>
}
interface RateSelectionState {
selectedRates: (RateCode | undefined)[]
roomsAndGuests: RoomParam[]
rateSummary: (Rate | null)[]
modifyRate: (index: number) => void
selectRate: (index: number, rate: RateCode | undefined) => void
setRoomsAndGuests: (rooms: RoomParam[]) => void
initializeRates: (count: number) => void
calculateRateSummary: ({
getFilteredRooms,
availablePackages,
roomCategories,
}: RateSummaryParams) => void
}
export const useRateSelectionStore = create<RateSelectionState>((set, get) => ({
selectedRates: [],
roomsAndGuests: [],
rateSummary: [],
modifyRate: (index) =>
set((state) => {
const newRates = [...state.selectedRates]
newRates[index] = undefined
return { selectedRates: newRates }
}),
selectRate: (index, rate) =>
set((state) => {
const newRates = [...state.selectedRates]
newRates[index] = rate
return { selectedRates: newRates }
}),
initializeRates: (count) =>
set({ selectedRates: new Array(count).fill(undefined) }),
setRoomsAndGuests: (rooms) => set({ roomsAndGuests: rooms }),
calculateRateSummary: ({
getFilteredRooms,
availablePackages,
roomCategories,
selectedPackagesByRoom,
}) => {
const state = get()
const summaries = state.roomsAndGuests.map((_, roomIndex) => {
const selectedRate = state.selectedRates[roomIndex]
const filteredRooms = getFilteredRooms(roomIndex)
const selectedPackages = selectedPackagesByRoom[roomIndex] || []
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
)
)
return {
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,
}
})
set({ rateSummary: summaries })
},
}))

View File

@@ -0,0 +1,58 @@
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type {
Rate,
RateCode,
} from "@/types/components/hotelReservation/selectRate/selectRate"
import type { RateSummaryParams } from "./rate-selection"
interface CalculateRoomSummaryParams extends RateSummaryParams {
selectedRate: RateCode
roomIndex: number
}
export function calculateRoomSummary({
selectedRate,
roomIndex,
getFilteredRooms,
availablePackages,
roomCategories,
selectedPackagesByRoom,
}: CalculateRoomSummaryParams): Rate | null {
const filteredRooms = getFilteredRooms(roomIndex)
const selectedPackages = selectedPackagesByRoom[roomIndex] || []
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((type) => type.code === room.roomTypeCode)
)
return {
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,
}
}

View File

@@ -0,0 +1,67 @@
import { create } from "zustand"
import { calculateRoomSummary } from "./helper"
import type {
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"
export interface RateSummaryParams {
getFilteredRooms: (roomIndex: number) => RoomConfiguration[]
availablePackages: RoomPackageData
roomCategories: Array<{ name: string; roomTypes: Array<{ code: string }> }>
selectedPackagesByRoom: Record<number, RoomPackageCodeEnum[]>
}
interface RateSelectionState {
selectedRates: (RateCode | undefined)[]
rateSummary: (Rate | null)[]
modifyRate: (index: number) => void
selectRate: (index: number, rate: RateCode | undefined) => void
initializeRates: (count: number) => void
calculateRateSummary: ({
getFilteredRooms,
availablePackages,
roomCategories,
}: RateSummaryParams) => void
}
export const useRateSelectionStore = create<RateSelectionState>((set, get) => ({
selectedRates: [],
rateSummary: [],
modifyRate: (index) =>
set((state) => {
const newRates = [...state.selectedRates]
newRates[index] = undefined
return { selectedRates: newRates }
}),
selectRate: (index, rate) =>
set((state) => {
const newRates = [...state.selectedRates]
newRates[index] = rate
return { selectedRates: newRates }
}),
initializeRates: (count) =>
set({ selectedRates: new Array(count).fill(undefined) }),
calculateRateSummary: (params) => {
const { selectedRates } = get()
const summaries = selectedRates.map((selectedRate, roomIndex) => {
if (!selectedRate) return null
return calculateRoomSummary({
selectedRate,
roomIndex,
...params,
})
})
set({ rateSummary: summaries })
},
}))

View File

@@ -19,7 +19,8 @@ export type FlexibilityOptionProps = {
priceInformation?: Array<string>
roomTypeCode: RoomConfiguration["roomTypeCode"]
petRoomPackage: RoomPackage | undefined
roomListIndex: number
isSelected: boolean
onSelect: () => void
}
export interface PriceListProps {

View File

@@ -21,7 +21,7 @@ export interface RoomFilterProps {
numberOfRooms: number
onFilter: (filter: Record<string, boolean | undefined>) => void
filterOptions: DefaultFilterOptions[]
roomListIndex: number
initialFilterValues: FilterValues
}
export type RoomPackage = z.output<typeof packagesSchema>

View File

@@ -8,7 +8,7 @@ import type {
RoomPackageData,
} from "./roomFilter"
export interface RoomListProps {
export interface RoomTypeListProps {
roomsAvailability: RoomsAvailability
roomCategories: RoomData[]
availablePackages: RoomPackageData | undefined