diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index ccda67e21..90a96095b 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -61,7 +61,7 @@ export default async function SelectRatePage({ searchTerm: selectHotelParams.city ?? hotel?.name, arrivalDate: format(arrivalDate, "yyyy-MM-dd"), departureDate: format(departureDate, "yyyy-MM-dd"), - noOfAdults: adultsInRoom, + noOfAdults: adultsInRoom[0], // TODO: Handle multiple rooms noOfChildren: childrenInRoom?.length, ageOfChildren: childrenInRoom?.map((c) => c.age).join(","), childBedPreference: childrenInRoom @@ -86,7 +86,7 @@ export default async function SelectRatePage({ lang={params.lang} fromDate={fromDate.toDate()} toDate={toDate.toDate()} - adultCount={adultsInRoom} + adultArray={adultsInRoom} childArray={childrenInRoom} /> @@ -99,7 +99,7 @@ export default async function SelectRatePage({ lang={params.lang} fromDate={fromDate.toDate()} toDate={toDate.toDate()} - adultCount={adultsInRoom} + adultArray={adultsInRoom} childArray={childrenInRoom} /> diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils.ts b/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils.ts index d4763b53d..d948eaaab 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils.ts +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils.ts @@ -23,7 +23,7 @@ interface HotelSearchDetails { city: Location | null hotel: HotelLocation | null selectHotelParams: SelectHotelParams & { city: string | undefined } - adultsInRoom: number + adultsInRoom: number[] childrenInRoomString?: string childrenInRoom?: Child[] } @@ -79,7 +79,7 @@ export async function getHotelSearchDetails< if (!city && !hotel) return notFound() if (isAlternativeHotels && (!city || !hotel)) return notFound() - let adultsInRoom = 1 + let adultsInRoom: number[] = [] let childrenInRoomString: HotelSearchDetails["childrenInRoomString"] = undefined let childrenInRoom: HotelSearchDetails["childrenInRoom"] = undefined @@ -87,7 +87,7 @@ export async function getHotelSearchDetails< const { rooms } = selectHotelParams if (rooms && rooms.length > 0) { - adultsInRoom = rooms[0].adults // TODO: Handle multiple rooms + adultsInRoom = rooms.map((room) => room.adults ?? 0) childrenInRoomString = rooms[0].childrenInRoom ? generateChildrenString(rooms[0].childrenInRoom) : undefined // TODO: Handle multiple rooms diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx b/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx index d4379e22b..ebe743617 100644 --- a/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx @@ -69,7 +69,7 @@ export async function SelectHotelMapContainer({ fetchAlternativeHotels(isAlternativeFor.id, { roomStayStartDate: selectHotelParams.fromDate, roomStayEndDate: selectHotelParams.toDate, - adults: adultsInRoom, + adults: adultsInRoom[0], children: childrenInRoomString, }) ) @@ -78,7 +78,7 @@ export async function SelectHotelMapContainer({ cityId: city.id, roomStayStartDate: selectHotelParams.fromDate, roomStayEndDate: selectHotelParams.toDate, - adults: adultsInRoom, + adults: adultsInRoom[0], children: childrenInRoomString, }) ) @@ -118,7 +118,7 @@ export async function SelectHotelMapContainer({ : (selectHotelParams.city as string), arrivalDate: format(arrivalDate, "yyyy-MM-dd"), departureDate: format(departureDate, "yyyy-MM-dd"), - noOfAdults: adultsInRoom, + noOfAdults: adultsInRoom[0], // TODO: Handle multiple rooms noOfChildren: childrenInRoom?.length, ageOfChildren: childrenInRoom?.map((c) => c.age).join(","), childBedPreference: childrenInRoom diff --git a/components/HotelReservation/SelectHotel/index.tsx b/components/HotelReservation/SelectHotel/index.tsx index e8fbbcaef..983a91017 100644 --- a/components/HotelReservation/SelectHotel/index.tsx +++ b/components/HotelReservation/SelectHotel/index.tsx @@ -83,7 +83,7 @@ export default async function SelectHotel({ fetchAlternativeHotels(isAlternativeFor.id, { roomStayStartDate: selectHotelParams.fromDate, roomStayEndDate: selectHotelParams.toDate, - adults: adultsInRoom, + adults: adultsInRoom[0], children: childrenInRoomString, }) ) @@ -92,7 +92,7 @@ export default async function SelectHotel({ cityId: city.id, roomStayStartDate: selectHotelParams.fromDate, roomStayEndDate: selectHotelParams.toDate, - adults: adultsInRoom, + adults: adultsInRoom[0], children: childrenInRoomString, }) ) @@ -167,7 +167,7 @@ export default async function SelectHotel({ : (selectHotelParams.city as string), arrivalDate: format(arrivalDate, "yyyy-MM-dd"), departureDate: format(departureDate, "yyyy-MM-dd"), - noOfAdults: adultsInRoom, + noOfAdults: adultsInRoom[0], // TODO: Handle multiple rooms, noOfChildren: childrenInRoom?.length, ageOfChildren: childrenInRoom?.map((c) => c.age).join(","), childBedPreference: childrenInRoom diff --git a/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx index 2badd5fce..3aee40107 100644 --- a/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx +++ b/components/HotelReservation/SelectRate/HotelInfoCard/NoRoomsAlert.tsx @@ -6,6 +6,7 @@ import { getIntl } from "@/i18n" import { safeTry } from "@/utils/safeTry" import { generateChildrenString } from "../../utils" +import { combineRoomAvailabilities } from "../utils" import styles from "./NoRoomsAlert.module.css" @@ -16,7 +17,7 @@ import type { Lang } from "@/constants/languages" type Props = { hotelId: number lang: Lang - adultCount: number + adultArray: number[] childArray?: Child[] fromDate: Date toDate: Date @@ -27,24 +28,39 @@ export async function NoRoomsAlert({ fromDate, toDate, childArray, - adultCount, + adultArray, lang, }: Props) { - const [availability, availabilityError] = await safeTry( - getRoomsAvailability({ - hotelId: hotelId, - roomStayStartDate: dt(fromDate).format("YYYY-MM-DD"), - roomStayEndDate: dt(toDate).format("YYYY-MM-DD"), - adults: adultCount, - children: childArray ? generateChildrenString(childArray) : undefined, // TODO: Handle multiple rooms, - }) - ) + const fromDateString = dt(fromDate).format("YYYY-MM-DD") + const toDateString = dt(toDate).format("YYYY-MM-DD") - if (!availability || availabilityError) { + const uniqueAdultCounts = [...new Set(adultArray)] + const roomsAvailabilityPromises = uniqueAdultCounts.map((adultCount) => { + return safeTry( + getRoomsAvailability({ + hotelId: hotelId, + roomStayStartDate: fromDateString, + roomStayEndDate: toDateString, + adults: adultCount, + children: + childArray && childArray.length > 0 + ? generateChildrenString(childArray) + : undefined, + }) + ) + }) + + const roomsAvailabilityResults = await Promise.all(roomsAvailabilityPromises) + + const roomsAvailability = combineRoomAvailabilities({ + availabilityResults: roomsAvailabilityResults, + }) + + if (!roomsAvailability) { return null } - const noRoomsAvailable = availability.roomConfigurations.reduce( + const noRoomsAvailable = roomsAvailability.roomConfigurations.reduce( (acc, room) => { return acc && room.status === "NotAvailable" }, diff --git a/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx b/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx index 723541b6a..322361f79 100644 --- a/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx +++ b/components/HotelReservation/SelectRate/Rooms/RoomsContainer.tsx @@ -10,13 +10,14 @@ import { safeTry } from "@/utils/safeTry" import { isValidSession } from "@/utils/session" import { generateChildrenString } from "../../utils" +import { combineRoomAvailabilities } from "../utils" import Rooms from "." import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import type { RoomsContainerProps } from "@/types/components/hotelReservation/selectRate/roomsContainer" export async function RoomsContainer({ - adultCount, + adultArray, childArray, fromDate, hotelId, @@ -42,7 +43,7 @@ export async function RoomsContainer({ hotelId: hotelId.toString(), startDate: fromDateString, endDate: toDateString, - adults: adultCount, + adults: adultArray[0], children: childArray ? childArray.length : undefined, packageCodes: [ RoomPackageCodeEnum.ACCESSIBILITY_ROOM, @@ -52,35 +53,35 @@ export async function RoomsContainer({ }) ) - const roomsAvailabilityPromise = safeTry( - getRoomsAvailability({ - hotelId: hotelId, - roomStayStartDate: fromDateString, - roomStayEndDate: toDateString, - adults: adultCount, - children: - childArray && childArray.length > 0 - ? generateChildrenString(childArray) - : undefined, - }) - ) + const uniqueAdultCounts = [...new Set(adultArray)] + const roomsAvailabilityPromises = uniqueAdultCounts.map((adultCount) => { + return safeTry( + getRoomsAvailability({ + hotelId: hotelId, + roomStayStartDate: fromDateString, + roomStayEndDate: toDateString, + adults: adultCount, + children: + childArray && childArray.length > 0 + ? generateChildrenString(childArray) + : undefined, + }) + ) + }) const [hotelData, hotelDataError] = await hotelDataPromise const [packages, packagesError] = await packagesPromise - const [roomsAvailability, roomsAvailabilityError] = - await roomsAvailabilityPromise + const roomsAvailabilityResults = await Promise.all(roomsAvailabilityPromises) + + const roomsAvailability = combineRoomAvailabilities({ + availabilityResults: roomsAvailabilityResults, + }) if (packagesError) { // TODO: Log packages error console.error("[RoomsContainer] unable to fetch packages") } - if (roomsAvailabilityError) { - // TODO: show proper error component - console.error("[RoomsContainer] unable to fetch room availability") - return null - } - if (!roomsAvailability) { // HotelInfoCard has the logic for displaying when there are no rooms available return null diff --git a/components/HotelReservation/SelectRate/Rooms/utils.ts b/components/HotelReservation/SelectRate/Rooms/utils.ts index 5a4c2f62f..fdedec453 100644 --- a/components/HotelReservation/SelectRate/Rooms/utils.ts +++ b/components/HotelReservation/SelectRate/Rooms/utils.ts @@ -1,3 +1,4 @@ +import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel" import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability" /** @@ -7,12 +8,17 @@ import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailabil export function filterDuplicateRoomTypesByLowestPrice( roomConfigurations: RoomConfiguration[] ): RoomConfiguration[] { - const roomTypeCount = roomConfigurations.reduce( - (acc, room) => { - acc[room.roomType] = (acc[room.roomType] || 0) + 1 - return acc + const roomTypeCount = roomConfigurations.reduce>( + (roomTypeTally, currentRoom) => { + const currentRoomType = currentRoom.roomType + const currentCount = roomTypeTally[currentRoomType] || 0 + + return { + ...roomTypeTally, + [currentRoomType]: currentCount + 1, + } }, - {} as Record + {} ) const duplicateRoomTypes = new Set( @@ -22,83 +28,105 @@ export function filterDuplicateRoomTypesByLowestPrice( const roomMap = new Map() roomConfigurations.forEach((room) => { - const { roomType, products } = room + const { roomType, products, status } = room if (!duplicateRoomTypes.has(roomType)) { roomMap.set(roomType, room) return } - products.forEach((product) => { - const { productType } = product - const publicProduct = productType.public || { - requestedPrice: null, - localPrice: null, - } - const memberProduct = productType.member || { - requestedPrice: null, - localPrice: null, - } + const previousRoom = roomMap.get(roomType) - const { - requestedPrice: publicRequestedPrice, - localPrice: publicLocalPrice, - } = publicProduct - const { - requestedPrice: memberRequestedPrice, - localPrice: memberLocalPrice, - } = memberProduct + // Prioritize 'Available' status + if ( + status === AvailabilityEnum.Available && + previousRoom?.status === AvailabilityEnum.NotAvailable + ) { + roomMap.set(roomType, room) + return + } - const previousLowest = roomMap.get(roomType) + if ( + status === AvailabilityEnum.NotAvailable && + previousRoom?.status === AvailabilityEnum.Available + ) { + return + } - 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 (previousRoom) { + products.forEach((product) => { + const { productType } = product + const publicProduct = productType.public || { + requestedPrice: null, + localPrice: null, + } + const memberProduct = productType.member || { + requestedPrice: null, + localPrice: null, + } - if ( - !previousLowest || - currentRequestedPrice < - Math.min( - Number( - previousLowest.products[0].productType.public.requestedPrice - ?.pricePerNight - ) ?? Infinity, - Number( - previousLowest.products[0].productType.member?.requestedPrice - ?.pricePerNight - ) ?? Infinity - ) || - (currentRequestedPrice === - Math.min( - Number( - previousLowest.products[0].productType.public.requestedPrice - ?.pricePerNight - ) ?? Infinity, - Number( - previousLowest.products[0].productType.member?.requestedPrice - ?.pricePerNight - ) ?? Infinity - ) && - currentLocalPrice < + 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].productType.public.localPrice + previousLowest.products[0].productType.public.requestedPrice ?.pricePerNight ) ?? Infinity, Number( - previousLowest.products[0].productType.member?.localPrice + previousLowest.products[0].productType.member?.requestedPrice ?.pricePerNight ) ?? Infinity - )) - ) { - roomMap.set(roomType, room) - } - }) + ) || + (currentRequestedPrice === + Math.min( + Number( + previousLowest.products[0].productType.public.requestedPrice + ?.pricePerNight + ) ?? Infinity, + Number( + previousLowest.products[0].productType.member?.requestedPrice + ?.pricePerNight + ) ?? Infinity + ) && + currentLocalPrice < + Math.min( + Number( + previousLowest.products[0].productType.public.localPrice + ?.pricePerNight + ) ?? Infinity, + Number( + previousLowest.products[0].productType.member?.localPrice + ?.pricePerNight + ) ?? Infinity + )) + ) { + roomMap.set(roomType, room) + } + }) + } else { + roomMap.set(roomType, room) + } }) return Array.from(roomMap.values()) diff --git a/components/HotelReservation/SelectRate/utils.ts b/components/HotelReservation/SelectRate/utils.ts new file mode 100644 index 000000000..1ce88b7c6 --- /dev/null +++ b/components/HotelReservation/SelectRate/utils.ts @@ -0,0 +1,23 @@ +import type { RoomsAvailability } from "@/types/trpc/routers/hotel/roomAvailability" + +export function combineRoomAvailabilities({ + availabilityResults, +}: { + availabilityResults: Array<[RoomsAvailability | undefined | null, unknown]> +}): RoomsAvailability | null { + return availabilityResults.reduce( + (combinedResult, [currentResult, error]) => { + if (error || !currentResult) return combinedResult + if (!combinedResult) return currentResult + + return { + ...currentResult, + roomConfigurations: [ + ...combinedResult.roomConfigurations, + ...currentResult.roomConfigurations, + ], + } + }, + null + ) +} diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index fbfc29b64..43c0d996d 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -55,6 +55,7 @@ import { } from "./utils" import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType" +import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel" import { BreakfastPackageEnum } from "@/types/enums/breakfast" import { HotelTypeEnum } from "@/types/enums/hotelType" import type { RequestOptionsWithOutBody } from "@/types/fetch" @@ -675,14 +676,14 @@ export const hotelQueryRouter = router({ validateAvailabilityData.data.roomConfigurations.filter((room) => { if (packageCodes) { return ( - room.status === "Available" && + room.status === AvailabilityEnum.Available && room.features.some( (feature) => packageCodes.includes(feature.code) && feature.inventory > 0 ) ) } - return room.status === "Available" + return room.status === AvailabilityEnum.Available }) const selectedRoom = availableRooms.find( diff --git a/types/components/hotelReservation/selectRate/hotelInfoCard.ts b/types/components/hotelReservation/selectRate/hotelInfoCard.ts index 8fc9a8c2d..9fcbcb486 100644 --- a/types/components/hotelReservation/selectRate/hotelInfoCard.ts +++ b/types/components/hotelReservation/selectRate/hotelInfoCard.ts @@ -2,7 +2,7 @@ import type { Lang } from "@/constants/languages" import type { Child } from "./selectRate" export interface HotelInfoCardProps { - adultCount: number + adultArray: number[] childArray?: Child[] fromDate: Date hotelId: number diff --git a/types/components/hotelReservation/selectRate/roomsContainer.ts b/types/components/hotelReservation/selectRate/roomsContainer.ts index 74f52aa2d..3935c0cb8 100644 --- a/types/components/hotelReservation/selectRate/roomsContainer.ts +++ b/types/components/hotelReservation/selectRate/roomsContainer.ts @@ -2,7 +2,7 @@ import type { Lang } from "@/constants/languages" import type { Child } from "./selectRate" export interface RoomsContainerProps { - adultCount: number + adultArray: number[] childArray?: Child[] fromDate: Date hotelId: number