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()((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()) 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(selector: (store: RatesState) => T) { const store = useContext(RatesContext) if (!store) { throw new Error("useRatesStore must be used within RatesProvider") } return useStore(store, selector) }