Files
web/apps/scandic-web/stores/select-rate/index.ts
2025-03-17 09:47:42 +01:00

318 lines
9.3 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 { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { RateTypeEnum } from "@/types/enums/rateType"
import type {
AvailabilityError,
InitialState,
RatesState,
} from "@/types/stores/rates"
import type { RoomConfiguration } from "@/types/trpc/routers/hotel/roomAvailability"
function findSelectedRate(
rateCode: string,
roomTypeCode: string,
rooms: RoomConfiguration[] | AvailabilityError
) {
if (!Array.isArray(rooms)) {
return null
}
return rooms.find(
(room) =>
room.roomTypeCode === roomTypeCode &&
room.products.find(
(product) =>
product.public?.rateCode === rateCode ||
product.member?.rateCode === rateCode
)
)
}
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"] = []
booking.rooms.forEach((room, idx) => {
if (room.rateCode && room.roomTypeCode) {
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
)
)
const product = selectedRoom?.products.find(
(p) =>
p.public?.rateCode === room.rateCode ||
p.member?.rateCode === room.rateCode
)
if (selectedRoom && product) {
rateSummary[idx] = {
features: selectedRoom.features,
member: product.member,
public: product.public,
rate: product.rate,
roomType: selectedRoom.roomType,
roomTypeCode: selectedRoom.roomTypeCode,
}
}
}
})
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) => ({
actions: {
closeSection(idx) {
return function () {
return set(
produce((state: RatesState) => {
if (state.rateSummary.length === state.booking.rooms.length) {
state.activeRoom = -1
} else {
state.activeRoom = idx + 1
}
})
)
}
},
modifyRate(idx) {
return function () {
return set(
produce((state: RatesState) => {
state.activeRoom = idx
})
)
}
},
selectFilter(idx) {
return function (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(idx) {
return function (selectedRate) {
return set(
produce((state: RatesState) => {
const memberRate = selectedRate.product.member
const publicRate = selectedRate.product.public
if (!memberRate && !publicRate) {
return
}
state.rooms[idx].selectedRate = selectedRate
state.rateSummary[idx] = {
features: selectedRate.features,
member: selectedRate.product.member,
package: state.rooms[idx].selectedPackage,
rate: selectedRate.product.rate,
public: selectedRate.product.public,
roomType: selectedRate.roomType,
roomTypeCode: selectedRate.roomTypeCode,
}
const isBookingCodeRate =
selectedRate.product.public?.rateType !== RateTypeEnum.Regular
const roomNr = idx + 1
const isMainRoom = roomNr + 1
const isMemberRate =
isUserLoggedIn && isMainRoom && memberRate && !isBookingCodeRate
const searchParams = new URLSearchParams(state.searchParams)
const counterratecode = isMemberRate
? (publicRate?.rateCode ?? "")
: (memberRate?.rateCode ?? "")
if (counterratecode) {
searchParams.set(
`room[${idx}].counterratecode`,
counterratecode
)
}
const rateCode = isMemberRate
? memberRate.rateCode
: (publicRate?.rateCode ?? "")
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}`
)
})
)
}
},
},
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
const product = selectedRate?.products.find(
(prd) =>
prd.public?.rateCode === room.rateCode ||
prd.member?.rateCode === room.rateCode
)
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,
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)
}