feat: prevent users from selecting the same room when there is no vacancy for it

This commit is contained in:
Simon Emanuelsson
2025-05-16 10:39:30 +02:00
committed by Michael Zetterberg
parent 4f0c61f68f
commit 1f1bcd480b
6 changed files with 93 additions and 22 deletions

View File

@@ -3,7 +3,9 @@
import { useRatesStore } from "@/stores/select-rate" import { useRatesStore } from "@/stores/select-rate"
import { RoomContext } from "@/contexts/SelectRate/Room" import { RoomContext } from "@/contexts/SelectRate/Room"
import { sortRoomConfigs } from "@/utils/sort"
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { RoomProviderProps } from "@/types/providers/select-rate/room" import type { RoomProviderProps } from "@/types/providers/select-rate/room"
@@ -12,19 +14,63 @@ export default function RoomProvider({
idx, idx,
room, room,
}: RoomProviderProps) { }: RoomProviderProps) {
const { activeRoom, roomAvailability, roomPackages } = useRatesStore( const { activeRoom, rateSummary, roomAvailability, roomPackages } =
(state) => ({ useRatesStore((state) => ({
activeRoom: state.activeRoom, activeRoom: state.activeRoom,
rateSummary: state.rateSummary,
roomPackages: state.roomsPackages[idx], roomPackages: state.roomsPackages[idx],
roomAvailability: state.roomsAvailability?.[idx], roomAvailability: state.roomsAvailability?.[idx],
}) }))
)
const roomNr = idx + 1 const roomNr = idx + 1
const petRoomPackage = roomPackages.find( const petRoomPackage = roomPackages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM (pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
) )
const selectedRoomsCount = rateSummary.reduce<Record<string, number>>(
(roomsCount, selectedRoom) => {
if (selectedRoom) {
if (!roomsCount[selectedRoom.roomTypeCode]) {
roomsCount[selectedRoom.roomTypeCode] = 0
}
roomsCount[selectedRoom.roomTypeCode] =
roomsCount[selectedRoom.roomTypeCode] + 1
}
return roomsCount
},
{}
)
const rooms = room.rooms
.map((r) => {
// Need to copy (shallow) since using immer with
// Zustand uses Proxy objects which results in
// properties being read-only thus causing errors
// when trying to modify roomsLeft etc.
const rCopy = { ...r }
if (selectedRoomsCount[r.roomTypeCode]) {
rCopy.roomsLeft =
rCopy.roomsLeft - selectedRoomsCount[rCopy.roomTypeCode]
const selectedRoom = rateSummary[idx]
const isCurrentlySelectedRoom =
selectedRoom && selectedRoom.roomTypeCode === rCopy.roomTypeCode
if (isCurrentlySelectedRoom) {
// Need to add 1 roomsLeft back to tally since
// our selectedRoom was included in selectedRoomsCount
rCopy.roomsLeft = rCopy.roomsLeft + 1
}
if (rCopy.roomsLeft <= 0) {
rCopy.status = AvailabilityEnum.NotAvailable
rCopy.roomsLeft = 0
}
}
return rCopy
})
.sort(sortRoomConfigs)
return ( return (
<RoomContext.Provider <RoomContext.Provider
value={{ value={{
@@ -35,6 +81,7 @@ export default function RoomProvider({
roomAvailability, roomAvailability,
roomPackages, roomPackages,
roomNr, roomNr,
rooms,
totalRooms: room.rooms.length, totalRooms: room.rooms.length,
}} }}
> >

View File

@@ -2,6 +2,7 @@ import { z } from "zod"
import { toLang } from "@/server/utils" import { toLang } from "@/server/utils"
import { sortRoomConfigs } from "@/utils/sort"
import { nullableStringValidator } from "@/utils/zod/stringValidator" import { nullableStringValidator } from "@/utils/zod/stringValidator"
import { occupancySchema } from "./schemas/availability/occupancy" import { occupancySchema } from "./schemas/availability/occupancy"
@@ -26,7 +27,6 @@ import {
import { relationshipsSchema } from "./schemas/relationships" import { relationshipsSchema } from "./schemas/relationships"
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration" import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition" import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
import { sortRoomConfigs } from "./utils"
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel" import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"

View File

@@ -12,6 +12,7 @@ import { toApiLang } from "@/server/utils"
import { generateChildrenString } from "@/components/HotelReservation/utils" import { generateChildrenString } from "@/components/HotelReservation/utils"
import { getCacheClient } from "@/services/dataCache" import { getCacheClient } from "@/services/dataCache"
import { cache } from "@/utils/cache" import { cache } from "@/utils/cache"
import { sortRoomConfigs } from "@/utils/sort"
import { getHotelPageUrls } from "../contentstack/hotelPage/utils" import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
import { type RoomFeaturesInput } from "./input" import { type RoomFeaturesInput } from "./input"
@@ -1215,18 +1216,6 @@ export function getSelectedRoomAvailability(
} }
} }
// Used to ensure `Available` rooms
// are shown before all `NotAvailable`
const statusLookup = {
[AvailabilityEnum.Available]: 1,
[AvailabilityEnum.NotAvailable]: 2,
}
export function sortRoomConfigs(a: RoomConfiguration, b: RoomConfiguration) {
// @ts-expect-error - array indexing
return statusLookup[a.status] - statusLookup[b.status]
}
export function getBedTypes( export function getBedTypes(
rooms: RoomConfiguration[], rooms: RoomConfiguration[],
roomType: string, roomType: string,

View File

@@ -128,6 +128,11 @@ export function createRatesStore({
pathname, pathname,
rateSummary, rateSummary,
roomConfigurations, roomConfigurations,
roomCategories,
roomsPackages,
roomsAvailability,
searchParams,
vat,
rooms: booking.rooms.map((room, idx) => { rooms: booking.rooms.map((room, idx) => {
const roomConfiguration = roomConfigurations[idx] const roomConfiguration = roomConfigurations[idx]
const roomPackages = roomsPackages[idx] const roomPackages = roomsPackages[idx]
@@ -304,6 +309,26 @@ export function createRatesStore({
}) })
) )
}, },
removeSelectedRoom() {
return set(
produce((state: RatesState) => {
state.rateSummary[idx] = null
const searchParams = state.searchParams
searchParams.delete(`room[${idx}].counterratecode`)
searchParams.delete(`room[${idx}].ratecode`)
searchParams.delete(`room[${idx}].roomtype`)
state.searchParams = searchParams
window.history.replaceState(
{},
"",
`${state.pathname}?${searchParams}`
)
})
)
},
selectFilter(filter) { selectFilter(filter) {
return set( return set(
produce((state: RatesState) => { produce((state: RatesState) => {
@@ -545,11 +570,6 @@ export function createRatesStore({
: null, : null,
} }
}), }),
roomCategories,
roomsPackages,
roomsAvailability,
searchParams,
vat,
} }
}) })
} }

View File

@@ -26,6 +26,7 @@ interface Actions {
modifyRate: () => void modifyRate: () => void
removeSelectedPackage: (code: PackageEnum) => void removeSelectedPackage: (code: PackageEnum) => void
removeSelectedPackages: () => void removeSelectedPackages: () => void
removeSelectedRoom: () => void
selectFilter: (filter: BookingCodeFilterEnum) => void selectFilter: (filter: BookingCodeFilterEnum) => void
selectPackages: (codes: PackageEnum[]) => void selectPackages: (codes: PackageEnum[]) => void
selectRate: (rate: SelectedRate, isUserLoggedIn: boolean) => void selectRate: (rate: SelectedRate, isUserLoggedIn: boolean) => void

View File

@@ -0,0 +1,14 @@
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
// Used to ensure `Available` rooms
// are shown before all `NotAvailable`
const statusLookup = {
[AvailabilityEnum.Available]: 1,
[AvailabilityEnum.NotAvailable]: 2,
}
export function sortRoomConfigs(a: RoomConfiguration, b: RoomConfiguration) {
// @ts-expect-error - array indexing
return statusLookup[a.status] - statusLookup[b.status]
}