Feat/SW-2113 allow feature combinations * feat(SW-2113): Refactor features data to be fetched on filter room filter change * feat(SW-2113): added loading state * fix: now clear room selection when applying filter and room doesnt exists. And added room features to mobile summary * fix * fix: add package to price details * feat(SW-2113): added buttons to room filter * fix: active room * fix: remove console log * fix: added form and close handler to room package filter * fix: add restriction so you cannot select pet room with allergy room and vice versa * fix: fixes from review feedback * fix * fix: hide modify button if on nextcoming rooms if no selection is made, and adjust filter logic in togglePackage * fix: forgot to use roomFeatureCodes from input.. * fix: naming Approved-by: Simon.Emanuelsson
471 lines
15 KiB
TypeScript
471 lines
15 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 {
|
|
filterRoomsBySelectedPackages,
|
|
findProductInRoom,
|
|
findSelectedRate,
|
|
} 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 {
|
|
PriceProduct,
|
|
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 selectedPackages = room.packages ?? []
|
|
|
|
let rooms: RoomConfiguration[] = filterRoomsBySelectedPackages(
|
|
selectedPackages,
|
|
roomConfiguration
|
|
)
|
|
|
|
const selectedRoom = findSelectedRate(
|
|
room.rateCode,
|
|
room.counterRateCode,
|
|
room.roomTypeCode,
|
|
rooms
|
|
)
|
|
|
|
if (!selectedRoom) {
|
|
continue
|
|
}
|
|
|
|
const product = findProductInRoom(
|
|
room.rateCode,
|
|
selectedRoom,
|
|
room.counterRateCode
|
|
)
|
|
if (product) {
|
|
rateSummary[idx] = {
|
|
features: selectedRoom.features,
|
|
product,
|
|
packages: room.packages ?? [],
|
|
rate: product.rate,
|
|
roomType: selectedRoom.roomType,
|
|
roomTypeCode: selectedRoom.roomTypeCode,
|
|
}
|
|
} else {
|
|
rateSummary[idx] = null
|
|
}
|
|
}
|
|
}
|
|
|
|
let activeRoom = rateSummary.length
|
|
if (searchParams.has("modifyRateIndex")) {
|
|
activeRoom = Number(searchParams.get("modifyRateIndex"))
|
|
} else if (rateSummary.length === booking.rooms.length) {
|
|
// Finds the first unselected room and sets that to active
|
|
// if no unselected rooms it will return -1 and close all rooms
|
|
const unselectedRoomIndex = rateSummary.findIndex((rate) => !rate)
|
|
activeRoom = unselectedRoomIndex
|
|
}
|
|
|
|
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 selectedPackages = room.packages ?? []
|
|
|
|
let rooms: RoomConfiguration[] = filterRoomsBySelectedPackages(
|
|
selectedPackages,
|
|
roomConfiguration
|
|
)
|
|
|
|
const selectedRate =
|
|
findSelectedRate(
|
|
room.rateCode,
|
|
room.counterRateCode,
|
|
room.roomTypeCode,
|
|
roomConfiguration
|
|
) ?? null
|
|
|
|
let product = null
|
|
if (selectedRate) {
|
|
product = findProductInRoom(
|
|
room.rateCode,
|
|
selectedRate,
|
|
room.counterRateCode
|
|
)
|
|
}
|
|
|
|
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) {
|
|
let campaign = currentRoom.campaign
|
|
if (incomingRoom.campaign.length) {
|
|
const newCampaign = [
|
|
...campaign,
|
|
...incomingRoom.campaign,
|
|
].reduce((cpns, cpn) => {
|
|
if (cpns.has(cpn.rateDefinition.rateCode)) {
|
|
return cpns
|
|
}
|
|
cpns.set(cpn.rateDefinition.rateCode, cpn)
|
|
return cpns
|
|
}, new Map<string, PriceProduct>())
|
|
campaign = Array.from(newCampaign.values())
|
|
}
|
|
return {
|
|
...currentRoom,
|
|
campaign,
|
|
products: [
|
|
...currentRoom.products,
|
|
...incomingRoom.products,
|
|
],
|
|
regular: incomingRoom.regular,
|
|
}
|
|
}
|
|
|
|
return currentRoom
|
|
})
|
|
|
|
state.rooms[idx].rooms = updatedRooms
|
|
})
|
|
)
|
|
},
|
|
addRoomFeatures(roomFeatures) {
|
|
return set(
|
|
produce((state: RatesState) => {
|
|
const selectedPackages = state.rooms[idx].selectedPackages
|
|
const rateSummaryItem = state.rateSummary[idx]
|
|
|
|
state.roomConfigurations[idx].forEach((room) => {
|
|
const features = roomFeatures.find(
|
|
(feat) => feat.roomTypeCode === room.roomTypeCode
|
|
)?.features
|
|
|
|
if (features) {
|
|
room.features = features
|
|
|
|
if (rateSummaryItem) {
|
|
rateSummaryItem.packages = selectedPackages
|
|
rateSummaryItem.features = features
|
|
}
|
|
}
|
|
})
|
|
|
|
state.rateSummary[idx] = rateSummaryItem
|
|
|
|
state.rooms[idx].rooms = filterRoomsBySelectedPackages(
|
|
selectedPackages,
|
|
state.roomConfigurations[idx]
|
|
)
|
|
|
|
const selectedRate = findSelectedRate(
|
|
room.rateCode,
|
|
room.counterRateCode,
|
|
room.roomTypeCode,
|
|
state.rooms[idx].rooms
|
|
)
|
|
|
|
if (!selectedRate) {
|
|
state.rooms[idx].selectedRate = null
|
|
state.rateSummary[idx] = null
|
|
}
|
|
})
|
|
)
|
|
},
|
|
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
|
|
})
|
|
)
|
|
},
|
|
togglePackages(selectedPackages) {
|
|
return set(
|
|
produce((state: RatesState) => {
|
|
state.rooms[idx].selectedPackages = selectedPackages
|
|
const rateSummaryItem = state.rateSummary[idx]
|
|
|
|
const roomConfiguration = state.roomConfigurations[idx]
|
|
if (roomConfiguration) {
|
|
const searchParams = new URLSearchParams(state.searchParams)
|
|
if (selectedPackages.length) {
|
|
searchParams.set(
|
|
`room[${idx}].packages`,
|
|
selectedPackages.join(",")
|
|
)
|
|
|
|
if (rateSummaryItem) {
|
|
rateSummaryItem.packages = selectedPackages
|
|
}
|
|
} else {
|
|
state.rooms[idx].rooms = roomConfiguration
|
|
if (rateSummaryItem) {
|
|
rateSummaryItem.packages = []
|
|
}
|
|
searchParams.delete(`room[${idx}].packages`)
|
|
}
|
|
|
|
// If we already have the features data 'addRoomFeatures' wont run
|
|
// so we need to do additional filtering here if thats the case
|
|
const filteredRooms = filterRoomsBySelectedPackages(
|
|
selectedPackages,
|
|
state.roomConfigurations[idx]
|
|
)
|
|
|
|
if (filteredRooms.length) {
|
|
const selectedRate = findSelectedRate(
|
|
room.rateCode,
|
|
room.counterRateCode,
|
|
room.roomTypeCode,
|
|
state.rooms[idx].rooms
|
|
)
|
|
|
|
if (!selectedRate) {
|
|
state.rooms[idx].selectedRate = null
|
|
state.rateSummary[idx] = null
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|