Files
web/apps/scandic-web/stores/select-rate/index.ts
Tobias Johansson 7434f30c20 Merged in feat/SW-2033-implement-new-room-feature-request (pull request #1665)
feat(SW-2033): Added new route for fetching room features, and merged the data with existing availability data

* feat(SW-2033): Added new route for fetching room features, and merged the data with existing availability data

* fix: issue with total price not including room features

* fix: add return null

* fix

* fix

* fixes from PR feedback


Approved-by: Arvid Norlin
2025-03-31 11:20:49 +00:00

378 lines
12 KiB
TypeScript

import { produce } from "immer"
import { ReadonlyURLSearchParams } from "next/navigation"
import { useContext } from "react"
import { create, useStore } from "zustand"
import { RatesContext } from "@/contexts/Rates"
import {
findProductInRoom,
findSelectedRate,
isRoomPackageCode,
} from "./helpers"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
import { RateTypeEnum } from "@/types/enums/rateType"
import type { InitialState, RatesState } from "@/types/stores/rates"
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
export function createRatesStore({
booking,
hotelType,
isUserLoggedIn,
labels,
packages,
pathname,
roomCategories,
roomsAvailability,
searchParams,
vat,
}: InitialState) {
const filterOptions = [
{
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
description: labels.accessibilityRoom,
itemCode: packages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
)?.itemCode,
},
{
code: RoomPackageCodeEnum.ALLERGY_ROOM,
description: labels.allergyRoom,
itemCode: packages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM
)?.itemCode,
},
{
code: RoomPackageCodeEnum.PET_ROOM,
description: labels.petRoom,
itemCode: packages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
)?.itemCode,
},
]
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"] = []
for (const [idx, room] of booking.rooms.entries()) {
if (room.rateCode && room.roomTypeCode) {
const roomConfiguration = roomConfigurations?.[idx]
const selectedRoom = findSelectedRate(
room.rateCode,
room.roomTypeCode,
roomConfiguration
)
if (!selectedRoom) {
continue
}
const product = findProductInRoom(room.rateCode, selectedRoom)
if (product) {
rateSummary[idx] = {
features: selectedRoom.features,
product,
rate: product.rate,
roomType: selectedRoom.roomType,
roomTypeCode: selectedRoom.roomTypeCode,
package: room.packages?.[0],
}
}
}
}
let activeRoom = rateSummary.length
if (searchParams.has("modifyRateIndex")) {
activeRoom = Number(searchParams.get("modifyRateIndex"))
} else if (rateSummary.length === booking.rooms.length) {
// Since all rooms has selections, all sections should be
// closed on load
activeRoom = -1
}
return create<RatesState>()((set) => {
return {
activeRoom,
booking,
filterOptions,
hotelType,
isUserLoggedIn,
packages,
pathname,
petRoomPackage: packages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
),
rateSummary,
roomConfigurations,
rooms: booking.rooms.map((room, idx) => {
const roomConfiguration = roomConfigurations[idx]
const selectedRate =
findSelectedRate(
room.rateCode,
room.roomTypeCode,
roomConfiguration
) ?? null
let product = null
if (selectedRate) {
product = findProductInRoom(room.rateCode, selectedRate)
}
// Since features are fetched async based on query string, we need to read from query string to apply correct filtering
const packagesParam = searchParams.get(`room[${idx}].packages`)
const selectedPackage = isRoomPackageCode(packagesParam)
? packagesParam
: undefined
let rooms: RoomConfiguration[] = roomConfiguration
if (selectedPackage) {
rooms = roomConfiguration.filter((r) =>
r.features.find((f) => f.code === selectedPackage)
)
}
return {
actions: {
appendRegularRates(roomConfigurations) {
return set(
produce((state: RatesState) => {
const rooms = state.rooms[idx].rooms
const updatedRooms = rooms.map((currentRoom) => {
const incomingRoom = roomConfigurations.find(
(room) =>
room.roomType === currentRoom.roomType &&
room.roomTypeCode === currentRoom.roomTypeCode
)
if (incomingRoom) {
return {
...currentRoom,
campaign: [
...currentRoom.campaign,
...incomingRoom.campaign,
],
products: [
...currentRoom.products,
...incomingRoom.products,
],
regular: incomingRoom.regular,
}
}
return currentRoom
})
state.rooms[idx].rooms = updatedRooms
})
)
},
closeSection() {
return set(
produce((state: RatesState) => {
if (state.rateSummary.length === state.booking.rooms.length) {
state.activeRoom = -1
} else {
state.activeRoom = idx + 1
}
})
)
},
modifyRate() {
return set(
produce((state: RatesState) => {
state.activeRoom = idx
})
)
},
selectFilter(filter) {
return set(
produce((state: RatesState) => {
state.rooms[idx].selectedFilter = filter
})
)
},
selectPackage(code) {
return set(
produce((state: RatesState) => {
state.rooms[idx].selectedPackage = 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}`
)
}
})
)
},
selectRate(selectedRate) {
return set(
produce((state: RatesState) => {
if (!selectedRate.product) {
return
}
state.rooms[idx].selectedRate = selectedRate
state.rateSummary[idx] = {
features: selectedRate.features,
package: state.rooms[idx].selectedPackage,
product: selectedRate.product,
rate: selectedRate.product.rate,
roomType: selectedRate.roomType,
roomTypeCode: selectedRate.roomTypeCode,
}
const roomNr = idx + 1
const isMainRoom = roomNr === 1
let productRateCode = ""
if ("corporateCheque" in selectedRate.product) {
productRateCode =
selectedRate.product.corporateCheque.rateCode
}
if ("redemption" in selectedRate.product) {
productRateCode = selectedRate.product.redemption.rateCode
}
if ("voucher" in selectedRate.product) {
productRateCode = selectedRate.product.voucher.rateCode
}
let isRegularRate = false
if (
"public" in selectedRate.product &&
selectedRate.product.public
) {
isRegularRate =
selectedRate.product.public.rateType ===
RateTypeEnum.Regular
productRateCode = selectedRate.product.public.rateCode
}
let hasMemberRate = false
let memberRateCode = ""
if (
"member" in selectedRate.product &&
selectedRate.product.member
) {
hasMemberRate = true
memberRateCode = selectedRate.product.member.rateCode
}
const isMemberRate =
isUserLoggedIn &&
isMainRoom &&
hasMemberRate &&
isRegularRate
const searchParams = new URLSearchParams(state.searchParams)
const counterratecode = isMemberRate
? productRateCode
: memberRateCode
if (counterratecode) {
searchParams.set(
`room[${idx}].counterratecode`,
counterratecode
)
}
const rateCode = isMemberRate
? memberRateCode
: productRateCode
if (rateCode) {
searchParams.set(`room[${idx}].ratecode`, rateCode)
}
searchParams.set(
`room[${idx}].roomtype`,
selectedRate.roomTypeCode
)
if (state.rateSummary.length === state.booking.rooms.length) {
state.activeRoom = -1
} else {
state.activeRoom = idx + 1
}
state.searchParams = new ReadonlyURLSearchParams(searchParams)
window.history.pushState(
{},
"",
`${state.pathname}?${searchParams}`
)
})
)
},
},
bookingRoom: room,
rooms,
selectedFilter: booking.bookingCode
? BookingCodeFilterEnum.Discounted
: BookingCodeFilterEnum.All,
selectedPackage,
selectedRate:
selectedRate && product
? {
features: selectedRate.features,
product,
roomType: selectedRate.roomType,
roomTypeCode: selectedRate.roomTypeCode,
}
: null,
}
}),
roomCategories,
roomsAvailability,
searchParams,
vat,
}
})
}
export function useRatesStore<T>(selector: (store: RatesState) => T) {
const store = useContext(RatesContext)
if (!store) {
throw new Error("useRatesStore must be used within RatesProvider")
}
return useStore(store, selector)
}