UNPKG

@qite/tide-booking-component

Version:

React Booking wizard & Booking product component for Tide

604 lines (546 loc) 20.4 kB
import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { BookingAttributes, BookingOptions, GroupedFlights, ProductAttributes, } from "../../types"; import { AirlineBookingPackageOption, AirportBookingPackageOption, BookingAirlineGroup, BookingAirportGroup, BookingOptionGroup, BookingOptionPax, BookingOptionUnit, BookingPackage, BookingPackageDetailsRequest, BookingPackageFlight, BookingPackagePax, BookingPackageRequest, BookingPackageRequestRoom, BookingPackageRoom, BookingTravelAgent, GenerateBookingAccommodationRequest, PerBookingPackageOption, } from "@qite/tide-client/build/types"; import { first, isEmpty, isNil, range } from "lodash"; import { RootState } from "../../store"; import { selectAgentId } from "../travelers-form/travelers-form-slice"; import packageApi from "./api"; import { BookingStep, OPTIONS_FORM_STEP } from "./constants"; import { selectAccommodationCodes, selectBookingAttributes, selectBookingRooms, selectLanguageCode, selectOfficeId, selectProductAttributes, selectProductCode, } from "./selectors"; export interface BookingState { officeId: number; languageCode: string; productAttributes?: ProductAttributes; bookingAttributes?: BookingAttributes; calculateDeposit: boolean; bookingNumber?: string; isRetry: boolean; package?: BookingPackage; agents?: BookingTravelAgent[]; isBusy: boolean; skipPaymentWithAgent: boolean; generatePaymentUrl: boolean; isUnavailable?: boolean; tagIds?: number[]; agentAdressId?: number; remarks?: string; voucherCodes?: string[]; bookingOptions: BookingOptions; bookingType: string; currentStep: BookingStep; translations?: { language: string; value: any; }[]; accommodationViewId?: number; accommodationViews?: { [key: string]: string }; } const initialState: BookingState = { officeId: 1, languageCode: "nl-BE", bookingOptions: { b2b: { tagIds: [], entryStatus: 2, customEntryStatusId: undefined, }, b2b2c: { tagIds: [], entryStatus: 2, customEntryStatusId: undefined, }, b2c: { tagIds: [], entryStatus: 0, customEntryStatusId: undefined, }, }, bookingType: "b2c", productAttributes: undefined, bookingAttributes: undefined, calculateDeposit: false, bookingNumber: undefined, isRetry: false, package: undefined, isBusy: false, skipPaymentWithAgent: false, generatePaymentUrl: false, tagIds: [], agentAdressId: undefined, currentStep: OPTIONS_FORM_STEP, translations: undefined, }; export const fetchPackage = createAsyncThunk( "booking/fetchPackage", async (_, { dispatch }) => { dispatch(setFetchingPackage(true)); await dispatch(fetchAgents()); await dispatch(fetchPackageDetails()); await dispatch(fetchAccommodationViews()); dispatch(setFetchingPackage(false)); } ); const fetchAgents = createAsyncThunk( "booking/agents", async (_, { dispatch, getState, signal }) => { const settings = getState() as RootState; return await packageApi.fetchAgents(signal, settings.apiSettings); } ); const fetchPackageDetails = createAsyncThunk( "booking/details", async (_, { dispatch, getState, signal }) => { const state = getState() as RootState; const officeId = selectOfficeId(state); const productAttributes = selectProductAttributes(state); const bookingAttributes = selectBookingAttributes(state); const agentId = selectAgentId(state); const rooms = selectBookingRooms(state); const languageCode = selectLanguageCode(state); if (isNil(productAttributes)) { throw Error("productAttributes could not be found"); } if (isNil(bookingAttributes)) { throw Error("bookingAttributes could not be found"); } if (!rooms?.length) { throw Error("rooms could not be found"); } var requestRooms = rooms?.map((x, i) => { var room = { index: i, pax: [] } as BookingPackageRequestRoom; range(0, x.adults).forEach(() => { room.pax.push({ age: 30, } as BookingPackagePax); }); x.childAges.forEach((x) => { room.pax.push({ age: x, } as BookingPackagePax); }); return room; }); const isAllotment = bookingAttributes.tourCode || bookingAttributes.allotmentName || (bookingAttributes.allotmentIds && bookingAttributes.allotmentIds.length); const request = { officeId: officeId, agentId: agentId, payload: { searchType: isAllotment ? 1 : 0, catalogueId: bookingAttributes.catalog, productCode: productAttributes.productCode, fromDate: bookingAttributes.startDate, toDate: bookingAttributes.endDate, includeFlights: bookingAttributes.includeFlights, allotmentName: bookingAttributes.allotmentName, allotmentIds: bookingAttributes.allotmentIds ?? [], tourCode: bookingAttributes.tourCode, rooms: requestRooms, } as BookingPackageDetailsRequest, } as BookingPackageRequest<BookingPackageDetailsRequest>; return await packageApi.fetchDetails( request, signal, languageCode, state.apiSettings ); } ); const fetchAccommodationViews = createAsyncThunk( "booking/accommodationViews", async (_, { dispatch, getState, signal }) => { const state = getState() as RootState; if (!state.booking.accommodationViewId) return Promise.resolve(); const languageCode = selectLanguageCode(state); const accommodationCodes = selectAccommodationCodes(state); const productCode = selectProductCode(state); if (!productCode) { throw Error('No product selected'); } const request = { languageCode: languageCode, productCode: productCode, accommodationCodes: accommodationCodes, contentViewId: state.booking.accommodationViewId } as GenerateBookingAccommodationRequest; return await packageApi.fetchAccommodationViews(request, signal, state.apiSettings); } ); const getActiveOption = (state: BookingState) => { if (state.package) return state.package.options.find((x) => x.isSelected); return null; }; const changeOutwardFlight = (state: BookingPackage, flight: BookingPackageFlight) => { const currentOutwardFlight = state.outwardFlights.find(x => x.isSelected)!; const currentReturnFlight = state.returnFlights.find(x => x.isSelected)!; if (currentOutwardFlight?.entryLineGuid == flight.entryLineGuid) return; const newFlight = state.outwardFlights.find(x => x.entryLineGuid == flight.entryLineGuid); if (newFlight) { newFlight.isSelected = true; currentOutwardFlight.isSelected = false; if (newFlight.externalGuid) { if (currentOutwardFlight.externalGuid !== newFlight.externalGuid) { const newReturnFlight = state.returnFlights.find(x => x.externalGuid === newFlight.externalGuid)!; currentReturnFlight.isSelected = false; newReturnFlight.isSelected = true; } } else if (currentReturnFlight.externalGuid) { const firstInternal = state.returnFlights.find(x => !x.externalGuid); if (firstInternal) { currentReturnFlight.isSelected = false; firstInternal.isSelected = true; } } } } const changeReturnFlight = (state: BookingPackage, flight: BookingPackageFlight) => { const currentReturnFlight = state.returnFlights.find(x => x.isSelected)!; if (currentReturnFlight?.entryLineGuid == flight.entryLineGuid) return; const newFlight = state.outwardFlights.find(x => x.entryLineGuid == flight.entryLineGuid); if (newFlight) { newFlight.isSelected = true; currentReturnFlight.isSelected = false; } } const changePackageOption = (state: BookingPackage) => { const selectedOutward = state.outwardFlights.find(x => x.isSelected)!; const selectedReturn = state.returnFlights.find(x => x.isSelected)!; const validOptions = selectedOutward.validOptions.filter(x => selectedReturn.validOptions.some(y => x === y)); const currentOption = state.options.find(x => x.isSelected)!; if (validOptions.some(x => x === currentOption.id)) return; const firstOption = state.options.find(x => validOptions.some(y => y === x.id))!; currentOption.isSelected = false; firstOption.isSelected = true; const currentRooms = currentOption.rooms.map(r => { const selectedOption = r.options.find(o => o.isSelected)!; return { accommodation: selectedOption?.accommodationCode, regime: selectedOption?.regimeCode }; }); firstOption.rooms.forEach((r, i) => { const currentRoom = currentRooms[i]; const selectedOption = r.options.find(o => o.isSelected); const selection = r.options.find(x => x.accommodationCode === currentRoom.accommodation && x.regimeCode === currentRoom.regime); if (selection) { if (selection.entryLineGuid !== selectedOption?.entryLineGuid) { if (selectedOption) selectedOption.isSelected = false; selection.isSelected = true; } } else { const accommodationSelection = r.options.find(x => x.accommodationCode === currentRoom.accommodation); if (accommodationSelection) { if (accommodationSelection.entryLineGuid !== selectedOption?.entryLineGuid) { if (selectedOption) selectedOption.isSelected = false; accommodationSelection.isSelected = true; } } else { const firstOption = r.options[0]; if (firstOption.entryLineGuid !== selectedOption?.entryLineGuid) { if (selectedOption) selectedOption.isSelected = false; firstOption.isSelected = true; } } } }); } const bookingSlice = createSlice({ name: "booking", initialState, reducers: { setOfficeId(state, action: PayloadAction<number>) { state.officeId = action.payload; }, setLanguageCode(state, action: PayloadAction<string>) { state.languageCode = action.payload; }, setTranslations(state, action: PayloadAction<any>) { state.translations = action.payload; }, setBookingOptions(state, action: PayloadAction<BookingOptions>) { state.bookingOptions = action.payload; }, setBookingType(state, action: PayloadAction<string>) { state.bookingType = action.payload; }, setProductAttributes(state, action: PayloadAction<ProductAttributes>) { state.productAttributes = action.payload; }, setBookingAttributes(state, action: PayloadAction<BookingAttributes>) { state.bookingAttributes = action.payload; }, setCalculateDeposit(state, action: PayloadAction<boolean>) { state.calculateDeposit = action.payload; }, setBookingNumber(state, action: PayloadAction<string>) { state.bookingNumber = action.payload; }, setIsRetry(state, action: PayloadAction<boolean>) { state.isRetry = action.payload; }, setFetchingPackage(state, action: PayloadAction<boolean>) { state.isBusy = action.payload; }, setPackage(state, action: PayloadAction<BookingPackage>) { state.package = action.payload; }, setPackageRooms(state, action: PayloadAction<BookingPackageRoom[]>) { const option = getActiveOption(state); if (option) option.rooms = action.payload; }, setPackageOptionPax(state, action: PayloadAction<BookingOptionPax[]>) { const option = getActiveOption(state); if (option) option.optionPax = action.payload; }, setPackageOptionUnits(state, action: PayloadAction<BookingOptionUnit[]>) { const option = getActiveOption(state); if (option) option.optionUnits = action.payload; }, setSkipPayment(state, action: PayloadAction<boolean>) { state.skipPaymentWithAgent = action.payload; }, setGeneratePaymentUrl(state, action: PayloadAction<boolean>) { state.generatePaymentUrl = action.payload; }, setPackageGroups( state, action: PayloadAction<BookingOptionGroup<PerBookingPackageOption>[]> ) { const option = getActiveOption(state); if (option) option.groups = action.payload; }, setPackageAirlineGroups( state, action: PayloadAction<BookingAirlineGroup<AirlineBookingPackageOption>[]> ) { const option = getActiveOption(state); if (option) option.airlineGroups = action.payload; }, setPackageAirportGroups( state, action: PayloadAction<BookingAirportGroup<AirportBookingPackageOption>[]> ) { const option = getActiveOption(state); if (option) option.airportGroups = action.payload; }, setTagIds(state, action: PayloadAction<number[] | undefined>) { state.tagIds = action.payload; }, setAgentAdressId(state, action: PayloadAction<number | undefined>) { state.agentAdressId = action.payload; }, setBookingRemarks(state, action: PayloadAction<string>) { state.remarks = action.payload; }, setVoucherCodes(state, action: PayloadAction<string[]>) { state.voucherCodes = action.payload; }, setCurrentStep(state, action: PayloadAction<BookingStep>) { document.body.scrollTop = 0; // For Safari document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera state.currentStep = action.payload; }, setFlights(state, action: PayloadAction<GroupedFlights>) { if (!state.package) return; changeOutwardFlight(state.package, action.payload.selectedOutward); changeReturnFlight(state.package, action.payload.selectedReturn); changePackageOption(state.package); }, setAccommodationViewId(state, action: PayloadAction<number>) { state.accommodationViewId = action.payload; } }, extraReducers: (builder) => { builder.addCase(fetchPackageDetails.fulfilled, (state, action) => { if (action.payload) { if (action.payload.errorCode) { console.error( action.payload.errorCode, action.payload.errorMessage, action.payload.errorDetails ); state.isUnavailable = true; return; } if (!action.payload.payload) { state.isUnavailable = true; return; } const bookingRooms = state.bookingAttributes?.rooms; const flight = state.bookingAttributes?.flight; const packageDetails = action.payload.payload; let activeOption = packageDetails.options.find((x) => x.isSelected)!; if (flight) { const selectedOutward = packageDetails.outwardFlights.find( (x) => x.isSelected ); const outwardFlights = packageDetails.outwardFlights.filter( (x) => x.code === flight.outwardCode && x.flightMetaData.flightLines[0]!.flightClass === flight.outwardClass && x.flightMetaData.flightLines.map((y) => y.number).join(",") === flight.outwardNumbers.join(",") ); // ook bij identieke vertrekvluchten eerst kijken als de returnflight kan gekoppeld worden. // op die manier moet het juiste koppel selected worden. // Enkel werkend met externe vluchten let outwardFlight: BookingPackageFlight | undefined = undefined; if (!isEmpty(outwardFlights)) { const returnExternalGuids = packageDetails.returnFlights .filter( (x) => x.code === flight.returnCode && x.flightMetaData.flightLines[0]!.flightClass === flight.returnClass && x.flightMetaData.flightLines .map((y) => y.number) .join(",") === flight.returnNumbers.join(",") ) .map((f) => f.externalGuid) .filter((e) => e); outwardFlight = outwardFlights.find((o) => returnExternalGuids.includes(o.externalGuid) ); if (!outwardFlight) { outwardFlight = first(outwardFlights); } } if (selectedOutward && outwardFlight) { selectedOutward.isSelected = false; outwardFlight.isSelected = true; } const selectedReturn = packageDetails.returnFlights.find( (x) => x.isSelected ); const returnFlight = outwardFlight?.externalGuid ? packageDetails.returnFlights.find( (x) => x.externalGuid === outwardFlight?.externalGuid ) : packageDetails.returnFlights.find( (x) => x.code === flight.returnCode && x.flightMetaData.flightLines[0]!.flightClass === flight.returnClass && x.flightMetaData.flightLines .map((y) => y.number) .join(",") === flight.returnNumbers.join(",") ); if (selectedReturn && returnFlight) { selectedReturn.isSelected = false; returnFlight.isSelected = true; } if (outwardFlight && returnFlight) { if (!outwardFlight.validOptions.some((x) => x == activeOption.id)) { activeOption.isSelected = false; activeOption = packageDetails.options.find((x) => outwardFlight?.validOptions.some((y) => y === x.id) )!; activeOption.isSelected = true; } } } if ( activeOption && bookingRooms?.some((x) => x.accommodationCode || x.regimeCode) ) { bookingRooms.forEach((room, i) => { if (room.accommodationCode || room.regimeCode) { activeOption.rooms[i].options = activeOption.rooms[i].options.map( (ro) => ({ ...ro, isSelected: ro.accommodationCode == room.accommodationCode && ro.regimeCode == room.regimeCode, }) ); } // Fallback to an option that has the requested accommodation OR regime if the requested option is not available. If no fallback is available, select the first option. if (!activeOption.rooms[i].options.some(x => x.isSelected)) { const fallbackOption = activeOption.rooms[i].options.find(x => x.accommodationCode == room.accommodationCode || x.regimeCode == room.regimeCode); if (fallbackOption) { fallbackOption.isSelected = true; } else { activeOption.rooms[i].options[0].isSelected = true; } } }); } state.package = packageDetails; } }); builder.addCase(fetchAgents.fulfilled, (state, action) => { if (action.payload) { state.agents = action.payload; } }); builder.addCase(fetchAccommodationViews.fulfilled, (state, action) => { if (action.payload) { state.accommodationViews = action.payload; } }); }, }); export const { setOfficeId, setLanguageCode, setTranslations, setBookingOptions, setBookingType, setProductAttributes, setBookingAttributes, setCalculateDeposit, setBookingNumber, setIsRetry, setFetchingPackage, setPackage, setPackageRooms, setPackageOptionPax, setPackageOptionUnits, setPackageGroups, setSkipPayment, setGeneratePaymentUrl, setTagIds, setAgentAdressId, setBookingRemarks, setVoucherCodes, setCurrentStep, setPackageAirlineGroups, setPackageAirportGroups, setFlights, setAccommodationViewId } = bookingSlice.actions; export default bookingSlice.reducer;