Files
web/apps/scandic-web/stores/select-rate/index.ts
Erik Tiekstra df32c08350 feat(SW-2043): Added new room packages filter
* feat(SW-2043): Added new room packages filter

* fix(SW-2043): Fixed issue with not updating price when selecting pet room

Approved-by: Tobias Johansson
Approved-by: Matilda Landström
2025-04-01 09:54:09 +00:00

394 lines
13 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 packageOptions = [
{
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,
packages: room.packages ?? [],
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) => {
return {
activeRoom,
booking,
packageOptions,
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 selectedPackages = packagesParam
? packagesParam.split(",").filter(isRoomPackageCode)
: []
let rooms: RoomConfiguration[] = roomConfiguration
if (selectedPackages.length) {
rooms = roomConfiguration.filter((r) =>
selectedPackages.some((pkg) =>
r.features.find((f) => f.code === pkg)
)
)
}
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
})
)
},
togglePackage(code) {
return set(
produce((state: RatesState) => {
const isSelected =
state.rooms[idx].selectedPackages.includes(code)
const selectedPackages = isSelected
? state.rooms[idx].selectedPackages.filter(
(pkg) => pkg !== code
)
: [...state.rooms[idx].selectedPackages, code]
state.rooms[idx].selectedPackages = selectedPackages
const roomConfiguration = state.roomConfigurations[idx]
if (roomConfiguration) {
const searchParams = new URLSearchParams(state.searchParams)
if (selectedPackages.length) {
state.rooms[idx].rooms = roomConfiguration.filter(
(room) =>
selectedPackages.every((pkg) =>
room.features.find((feat) => feat.code === pkg)
)
)
searchParams.set(
`room[${idx}].packages`,
selectedPackages.join(",")
)
if (state.rateSummary[idx]) {
state.rateSummary[idx].packages = selectedPackages
}
} else {
state.rooms[idx].rooms = roomConfiguration
if (state.rateSummary[idx]) {
state.rateSummary[idx].packages = []
}
searchParams.delete(`room[${idx}].packages`)
}
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,
packages: state.rooms[idx].selectedPackages,
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,
selectedPackages,
selectedRate:
selectedRate && product
? {
features: selectedRate.features,
packages: selectedPackages,
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)
}