feat(SW-718) updates after PR comments
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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])
|
||||
@@ -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}>
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
@@ -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 (
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RoomCardSkeleton } from "../RoomList/RoomCard/RoomCardSkeleton"
|
||||
import { RoomCardSkeleton } from "../../SelectHotel/RoomCardSkeleton/RoomCardSkeleton"
|
||||
|
||||
import styles from "./RoomsContainerSkeleton.module.css"
|
||||
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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;
|
||||
|
||||
21
components/HotelReservation/SelectRate/Rooms/variants.ts
Normal file
21
components/HotelReservation/SelectRate/Rooms/variants.ts
Normal 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,
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -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(
|
||||
|
||||
@@ -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 })
|
||||
},
|
||||
}))
|
||||
58
stores/select-rate/helper.ts
Normal file
58
stores/select-rate/helper.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
67
stores/select-rate/rate-selection.ts
Normal file
67
stores/select-rate/rate-selection.ts
Normal 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 })
|
||||
},
|
||||
}))
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
RoomPackageData,
|
||||
} from "./roomFilter"
|
||||
|
||||
export interface RoomListProps {
|
||||
export interface RoomTypeListProps {
|
||||
roomsAvailability: RoomsAvailability
|
||||
roomCategories: RoomData[]
|
||||
availablePackages: RoomPackageData | undefined
|
||||
|
||||
Reference in New Issue
Block a user