@qite/tide-booking-component
Version:
React Booking wizard & Booking product component for Tide
119 lines (99 loc) • 4.76 kB
text/typescript
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from '../../store';
import { selectRequestRooms } from '../booking/selectors';
import { isEmpty, sum } from 'lodash';
import { PriceDetailsPerPaxType, PricePerPaxType } from '../../types';
import { BookingPackageRequestRoom, BookingPriceDetail } from '@qite/tide-client';
export const selectPriceDetails = (state: RootState) => state.priceDetails.priceDetails;
export const selectPackagePriceDetails = createSelector(selectPriceDetails, (priceDetails) => priceDetails.filter((priceDetail) => priceDetail.isInPackage));
export const selectSeparatePackagePriceDetails = createSelector(selectPackagePriceDetails, (pricedetails) => {
const result: BookingPriceDetail[] = [];
const filteredPriceDetails = pricedetails.filter((priceDetail) => priceDetail.isSeparate);
filteredPriceDetails.forEach((priceDetail) => {
const priceDetailToMerge = result.find(
(x) => x.productCode === priceDetail.productCode && x.accommodationCode === priceDetail.accommodationCode && x.productType === priceDetail.productType
);
if (priceDetailToMerge) {
priceDetailToMerge.total += priceDetail.total;
priceDetailToMerge.price += priceDetail.price;
} else {
result.push(Object.assign({}, priceDetail));
}
});
return result;
});
export const selectBasePrice = createSelector(selectPackagePriceDetails, (priceDetails) =>
sum(priceDetails.map((priceDetail) => priceDetail.price * priceDetail.amount))
);
export const selectSeparateExtraPriceDetails = createSelector(selectPriceDetails, (priceDetails) =>
priceDetails.filter((priceDetail) => !priceDetail.isInPackage && priceDetail.isSeparate)
);
export const selectTotalPrice = createSelector(selectBasePrice, selectSeparateExtraPriceDetails, (basePrice, separatePriceDetails) =>
sum([basePrice, ...separatePriceDetails.map((priceDetail) => priceDetail.price * priceDetail.amount)])
);
// Shared function to aggregate price and details per paxType
const aggregatePricePerPaxType = (priceDetails: BookingPriceDetail[], requestRooms: BookingPackageRequestRoom[] | undefined): PricePerPaxType[] => {
if (!requestRooms || isEmpty(requestRooms)) return [];
if (!priceDetails || isEmpty(priceDetails)) return [];
// Compute paxType by age: >=2 INFANT, >=11 CHILD, else ADULT
const paxTypeToPaxIds: Record<string, Set<number>> = {};
requestRooms.forEach((room) => {
room.pax.forEach((pax) => {
let paxType = 'ADULT';
if (typeof pax.age === 'number') {
if (pax.age <= 11 && pax.age > 2) {
paxType = 'CHILD';
} else if (pax.age <= 2) {
paxType = 'INFANT';
}
}
if (!paxTypeToPaxIds[paxType]) paxTypeToPaxIds[paxType] = new Set();
paxTypeToPaxIds[paxType].add(pax.id);
});
});
const result: PricePerPaxType[] = [];
Object.keys(paxTypeToPaxIds).forEach((paxType) => {
const paxIds = Array.from(paxTypeToPaxIds[paxType]);
let pricePerPaxType = 0;
const detailsMap: Record<string, PriceDetailsPerPaxType> = {};
priceDetails.forEach((detail) => {
if (!detail.showPrice || !detail.pricePerPax) return;
detail.pricePerPax.forEach((ppp) => {
if (paxIds.includes(ppp.paxId)) {
pricePerPaxType += ppp.price;
const descKey = detail.priceDescription || '';
if (!detailsMap[descKey]) {
detailsMap[descKey] = {
numberOfPax: 1,
description: detail.priceDescription || '',
price: ppp.price,
paxIds: [ppp.paxId]
} as PriceDetailsPerPaxType;
} else {
detailsMap[descKey].price += ppp.price;
if (!detailsMap[descKey].paxIds.includes(ppp.paxId)) {
detailsMap[descKey].paxIds.push(ppp.paxId);
detailsMap[descKey].numberOfPax += 1;
}
}
}
});
});
result.push({
paxType,
pricePerPaxType,
numberOfPax: paxIds.length,
details: Object.values(detailsMap)
});
});
return result;
};
export const selectBasePricePerPaxType = createSelector(selectPackagePriceDetails, selectRequestRooms, (priceDetails, requestRooms) =>
aggregatePricePerPaxType(priceDetails, requestRooms)
);
export const selectSeparateExtraPriceDetailsPerPaxType = createSelector(selectSeparateExtraPriceDetails, selectRequestRooms, (priceDetails, requestRooms) =>
aggregatePricePerPaxType(priceDetails, requestRooms)
);
export const selectDeposit = (state: RootState) => state.priceDetails.deposit;
export const selectCommission = (state: RootState) => state.priceDetails.commission;
export const selectIsFetchingPriceDetails = (state: RootState) => state.priceDetails.isBusy;