feat: add multiroom tracking to booking flow
This commit is contained in:
@@ -211,12 +211,20 @@ export function calcTotalPrice(
|
||||
? parseInt(room.breakfast.localPrice?.price ?? 0)
|
||||
: 0
|
||||
|
||||
const roomFeaturesTotal = room.roomFeatures?.reduce((total, pkg) => {
|
||||
if (pkg.requestedPrice.totalPrice) {
|
||||
total = add(total, pkg.requestedPrice.totalPrice)
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
const roomFeaturesTotal = room.roomFeatures?.reduce(
|
||||
(total, pkg) => {
|
||||
if (pkg.requestedPrice.totalPrice) {
|
||||
total.requestedPrice = add(
|
||||
total.requestedPrice,
|
||||
pkg.requestedPrice.totalPrice
|
||||
)
|
||||
}
|
||||
total.local = add(total.local, pkg.localPrice.totalPrice)
|
||||
|
||||
return total
|
||||
},
|
||||
{ local: 0, requestedPrice: 0 }
|
||||
)
|
||||
|
||||
const result: Price = {
|
||||
requested: roomPrice.perStay.requested
|
||||
@@ -235,13 +243,13 @@ export function calcTotalPrice(
|
||||
acc.local.price,
|
||||
roomPrice.perStay.local.price,
|
||||
breakfastLocalPrice * room.adults * nights,
|
||||
roomFeaturesTotal
|
||||
roomFeaturesTotal?.local ?? 0
|
||||
),
|
||||
regularPrice: add(
|
||||
acc.local.regularPrice,
|
||||
roomPrice.perStay.local.regularPrice,
|
||||
breakfastLocalPrice * room.adults * nights,
|
||||
roomFeaturesTotal
|
||||
roomFeaturesTotal?.requestedPrice ?? 0
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -369,24 +369,33 @@ export function createDetailsStore(
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.rooms[idx].steps[StepEnum.details].isValid = true
|
||||
const currentRoom = state.rooms[idx].room
|
||||
|
||||
state.rooms[idx].room.guest.countryCode = data.countryCode
|
||||
state.rooms[idx].room.guest.dateOfBirth = data.dateOfBirth
|
||||
state.rooms[idx].room.guest.email = data.email
|
||||
state.rooms[idx].room.guest.firstName = data.firstName
|
||||
state.rooms[idx].room.guest.join = data.join
|
||||
state.rooms[idx].room.guest.lastName = data.lastName
|
||||
currentRoom.guest.countryCode = data.countryCode
|
||||
currentRoom.guest.email = data.email
|
||||
currentRoom.guest.firstName = data.firstName
|
||||
currentRoom.guest.join = data.join
|
||||
currentRoom.guest.lastName = data.lastName
|
||||
|
||||
if (data.join) {
|
||||
state.rooms[idx].room.guest.membershipNo = undefined
|
||||
currentRoom.guest.membershipNo = undefined
|
||||
} else {
|
||||
state.rooms[idx].room.guest.membershipNo = data.membershipNo
|
||||
currentRoom.guest.membershipNo = data.membershipNo
|
||||
}
|
||||
state.rooms[idx].room.guest.phoneNumber = data.phoneNumber
|
||||
state.rooms[idx].room.guest.zipCode = data.zipCode
|
||||
currentRoom.guest.phoneNumber = data.phoneNumber
|
||||
|
||||
state.rooms[idx].room.roomPrice = getRoomPrice(
|
||||
state.rooms[idx].room.roomRate,
|
||||
// Only valid for room 1
|
||||
if (idx === 0 && data.join && !isMember) {
|
||||
if ("dateOfBirth" in currentRoom.guest) {
|
||||
currentRoom.guest.dateOfBirth = data.dateOfBirth
|
||||
}
|
||||
if ("zipCode" in currentRoom.guest) {
|
||||
currentRoom.guest.zipCode = data.zipCode
|
||||
}
|
||||
}
|
||||
|
||||
currentRoom.roomPrice = getRoomPrice(
|
||||
currentRoom.roomRate,
|
||||
Boolean(data.join || data.membershipNo || isMember)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
|
||||
/**
|
||||
* Get the lowest priced room for each room type that appears more than once.
|
||||
*/
|
||||
|
||||
export function filterDuplicateRoomTypesByLowestPrice(
|
||||
roomConfigurations: RoomConfiguration[]
|
||||
): RoomConfiguration[] {
|
||||
const roomTypeCount = roomConfigurations.reduce<Record<string, number>>(
|
||||
(roomTypeTally, currentRoom) => {
|
||||
const currentRoomType = currentRoom.roomType
|
||||
const currentCount = roomTypeTally[currentRoomType] || 0
|
||||
|
||||
return {
|
||||
...roomTypeTally,
|
||||
[currentRoomType]: currentCount + 1,
|
||||
}
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
const duplicateRoomTypes = new Set(
|
||||
Object.keys(roomTypeCount).filter((roomType) => roomTypeCount[roomType] > 1)
|
||||
)
|
||||
|
||||
const roomMap = new Map()
|
||||
|
||||
roomConfigurations.forEach((room) => {
|
||||
const { roomType, products, status } = room
|
||||
|
||||
if (!duplicateRoomTypes.has(roomType)) {
|
||||
roomMap.set(roomType, room)
|
||||
return
|
||||
}
|
||||
|
||||
const previousRoom = roomMap.get(roomType)
|
||||
|
||||
// Prioritize 'Available' status
|
||||
if (
|
||||
status === AvailabilityEnum.Available &&
|
||||
previousRoom?.status === AvailabilityEnum.NotAvailable
|
||||
) {
|
||||
roomMap.set(roomType, room)
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
status === AvailabilityEnum.NotAvailable &&
|
||||
previousRoom?.status === AvailabilityEnum.Available
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (previousRoom) {
|
||||
products.forEach((product) => {
|
||||
const publicProduct = product?.public || {
|
||||
requestedPrice: null,
|
||||
localPrice: null,
|
||||
}
|
||||
const memberProduct = product?.member || {
|
||||
requestedPrice: null,
|
||||
localPrice: null,
|
||||
}
|
||||
|
||||
const {
|
||||
requestedPrice: publicRequestedPrice,
|
||||
localPrice: publicLocalPrice,
|
||||
} = publicProduct
|
||||
const {
|
||||
requestedPrice: memberRequestedPrice,
|
||||
localPrice: memberLocalPrice,
|
||||
} = memberProduct
|
||||
|
||||
const previousLowest = roomMap.get(roomType)
|
||||
|
||||
const currentRequestedPrice = Math.min(
|
||||
Number(publicRequestedPrice?.pricePerNight) ?? Infinity,
|
||||
Number(memberRequestedPrice?.pricePerNight) ?? Infinity
|
||||
)
|
||||
const currentLocalPrice = Math.min(
|
||||
Number(publicLocalPrice?.pricePerNight) ?? Infinity,
|
||||
Number(memberLocalPrice?.pricePerNight) ?? Infinity
|
||||
)
|
||||
|
||||
if (
|
||||
!previousLowest ||
|
||||
currentRequestedPrice <
|
||||
Math.min(
|
||||
Number(
|
||||
previousLowest.products[0].public?.requestedPrice?.pricePerNight
|
||||
) ?? Infinity,
|
||||
Number(
|
||||
previousLowest.products[0].member?.requestedPrice?.pricePerNight
|
||||
) ?? Infinity
|
||||
) ||
|
||||
(currentRequestedPrice ===
|
||||
Math.min(
|
||||
Number(
|
||||
previousLowest.products[0].public?.requestedPrice?.pricePerNight
|
||||
) ?? Infinity,
|
||||
Number(
|
||||
previousLowest.products[0].member?.requestedPrice?.pricePerNight
|
||||
) ?? Infinity
|
||||
) &&
|
||||
currentLocalPrice <
|
||||
Math.min(
|
||||
Number(
|
||||
previousLowest.products[0].public?.localPrice?.pricePerNight
|
||||
) ?? Infinity,
|
||||
Number(
|
||||
previousLowest.products[0].member?.localPrice?.pricePerNight
|
||||
) ?? Infinity
|
||||
))
|
||||
) {
|
||||
roomMap.set(roomType, room)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
roomMap.set(roomType, room)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(roomMap.values())
|
||||
}
|
||||
@@ -3,26 +3,25 @@ import { ReadonlyURLSearchParams } from "next/navigation"
|
||||
import { useContext } from "react"
|
||||
import { create, useStore } from "zustand"
|
||||
|
||||
import { filterDuplicateRoomTypesByLowestPrice } from "@/stores/select-rate/helper"
|
||||
|
||||
import { RatesContext } from "@/contexts/Rates"
|
||||
|
||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { RateTypeEnum } from "@/types/enums/rateType"
|
||||
import type { InitialState, RatesState } from "@/types/stores/rates"
|
||||
import type {
|
||||
AvailabilityError,
|
||||
InitialState,
|
||||
RatesState,
|
||||
} from "@/types/stores/rates"
|
||||
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
|
||||
const statusLookup = {
|
||||
[AvailabilityEnum.Available]: 1,
|
||||
[AvailabilityEnum.NotAvailable]: 2,
|
||||
}
|
||||
|
||||
function findSelectedRate(
|
||||
rateCode: string,
|
||||
roomTypeCode: string,
|
||||
rooms: RoomConfiguration[]
|
||||
rooms: RoomConfiguration[] | AvailabilityError
|
||||
) {
|
||||
if (!Array.isArray(rooms)) {
|
||||
return null
|
||||
}
|
||||
return rooms.find(
|
||||
(room) =>
|
||||
room.roomTypeCode === roomTypeCode &&
|
||||
@@ -70,23 +69,26 @@ export function createRatesStore({
|
||||
},
|
||||
]
|
||||
|
||||
let allRooms: RoomConfiguration[] = []
|
||||
if (roomsAvailability?.roomConfigurations) {
|
||||
allRooms = filterDuplicateRoomTypesByLowestPrice(
|
||||
roomsAvailability.roomConfigurations
|
||||
).sort(
|
||||
// @ts-expect-error - array indexing
|
||||
(a, b) => statusLookup[a.status] - statusLookup[b.status]
|
||||
)
|
||||
let roomConfigurations: RatesState["roomConfigurations"] = []
|
||||
if (roomsAvailability) {
|
||||
for (const availability of roomsAvailability) {
|
||||
if ("error" in availability) {
|
||||
// Availability request failed, default to empty array
|
||||
roomConfigurations.push([])
|
||||
} else {
|
||||
roomConfigurations.push(availability.roomConfigurations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rateSummary: RatesState["rateSummary"] = []
|
||||
booking.rooms.forEach((room, idx) => {
|
||||
if (room.rateCode && room.roomTypeCode) {
|
||||
const selectedRoom = roomsAvailability?.roomConfigurations.find(
|
||||
(roomConf) =>
|
||||
roomConf.roomTypeCode === room.roomTypeCode &&
|
||||
roomConf.products.find(
|
||||
const roomConfiguration = roomConfigurations?.[idx]
|
||||
const selectedRoom = roomConfiguration.find(
|
||||
(rc) =>
|
||||
rc.roomTypeCode === room.roomTypeCode &&
|
||||
rc.products.find(
|
||||
(product) =>
|
||||
product.public?.rateCode === room.rateCode ||
|
||||
product.member?.rateCode === room.rateCode
|
||||
@@ -149,31 +151,34 @@ export function createRatesStore({
|
||||
return set(
|
||||
produce((state: RatesState) => {
|
||||
state.rooms[idx].selectedPackage = code
|
||||
const searchParams = new URLSearchParams(state.searchParams)
|
||||
if (code) {
|
||||
state.rooms[idx].rooms = state.allRooms.filter((room) =>
|
||||
room.features.find((feat) => feat.code === code)
|
||||
const roomConfiguration = state.roomConfigurations[idx]
|
||||
if (roomConfiguration) {
|
||||
const searchParams = new URLSearchParams(state.searchParams)
|
||||
if (code) {
|
||||
state.rooms[idx].rooms = roomConfiguration.filter((room) =>
|
||||
room.features.find((feat) => feat.code === code)
|
||||
)
|
||||
searchParams.set(`room[${idx}].packages`, code)
|
||||
|
||||
if (state.rateSummary[idx]) {
|
||||
state.rateSummary[idx].package = code
|
||||
}
|
||||
} else {
|
||||
state.rooms[idx].rooms = roomConfiguration
|
||||
searchParams.delete(`room[${idx}].packages`)
|
||||
|
||||
if (state.rateSummary[idx]) {
|
||||
state.rateSummary[idx].package = undefined
|
||||
}
|
||||
}
|
||||
|
||||
state.searchParams = new ReadonlyURLSearchParams(searchParams)
|
||||
window.history.pushState(
|
||||
{},
|
||||
"",
|
||||
`${state.pathname}?${searchParams}`
|
||||
)
|
||||
searchParams.set(`room[${idx}].packages`, code)
|
||||
|
||||
if (state.rateSummary[idx]) {
|
||||
state.rateSummary[idx].package = code
|
||||
}
|
||||
} else {
|
||||
state.rooms[idx].rooms = state.allRooms
|
||||
searchParams.delete(`room[${idx}].packages`)
|
||||
|
||||
if (state.rateSummary[idx]) {
|
||||
state.rateSummary[idx].package = undefined
|
||||
}
|
||||
}
|
||||
|
||||
state.searchParams = new ReadonlyURLSearchParams(searchParams)
|
||||
window.history.pushState(
|
||||
{},
|
||||
"",
|
||||
`${state.pathname}?${searchParams}`
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -247,7 +252,6 @@ export function createRatesStore({
|
||||
},
|
||||
},
|
||||
activeRoom,
|
||||
allRooms,
|
||||
booking,
|
||||
filterOptions,
|
||||
hotelType,
|
||||
@@ -258,9 +262,12 @@ export function createRatesStore({
|
||||
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
||||
),
|
||||
rateSummary,
|
||||
rooms: booking.rooms.map((room) => {
|
||||
roomConfigurations,
|
||||
rooms: booking.rooms.map((room, idx) => {
|
||||
const roomConfiguration = roomConfigurations[idx]
|
||||
const selectedRate =
|
||||
findSelectedRate(room.rateCode, room.roomTypeCode, allRooms) ?? null
|
||||
findSelectedRate(room.rateCode, room.roomTypeCode, roomConfiguration) ??
|
||||
null
|
||||
|
||||
const product = selectedRate?.products.find(
|
||||
(prd) =>
|
||||
@@ -270,22 +277,25 @@ export function createRatesStore({
|
||||
|
||||
const selectedPackage = room.packages?.[0]
|
||||
|
||||
let rooms: RoomConfiguration[] = roomConfiguration
|
||||
if (selectedPackage) {
|
||||
rooms = roomConfiguration.filter((r) =>
|
||||
r.features.find((f) => f.code === selectedPackage)
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
bookingRoom: room,
|
||||
rooms: selectedPackage
|
||||
? allRooms.filter((r) =>
|
||||
r.features.find((f) => f.code === selectedPackage)
|
||||
)
|
||||
: allRooms,
|
||||
rooms,
|
||||
selectedPackage,
|
||||
selectedRate:
|
||||
selectedRate && product
|
||||
? {
|
||||
features: selectedRate.features,
|
||||
product,
|
||||
roomType: selectedRate.roomType,
|
||||
roomTypeCode: selectedRate.roomTypeCode,
|
||||
}
|
||||
features: selectedRate.features,
|
||||
product,
|
||||
roomType: selectedRate.roomType,
|
||||
roomTypeCode: selectedRate.roomTypeCode,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user