Merged in feat/SW-2113-allow-feature-combinations (pull request #1719)

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
This commit is contained in:
Tobias Johansson
2025-04-07 11:36:34 +00:00
parent 8d34e1c8bb
commit e6ae6ff650
31 changed files with 725 additions and 359 deletions

View File

@@ -16,16 +16,10 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { formatPrice } from "@/utils/numberFormatting"
import MobileSummary from "./MobileSummary"
import {
calculateCorporateChequePrice,
calculateRedemptionTotalPrice,
calculateTotalPrice,
calculateVoucherPrice,
} from "./utils"
import { getTotalPrice } from "./utils"
import styles from "./rateSummary.module.css"
import type { Price } from "@/types/components/hotelReservation/price"
import type { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { RateEnum } from "@/types/enums/rate"
@@ -96,9 +90,10 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
const summaryPriceText = `${totalNights}, ${totalAdults}${totalChildren}, ${totalRooms}`
const totalRoomsRequired = bookingRooms.length
const isAllRoomsSelected = rateSummary.length === totalRoomsRequired
const isAllRoomsSelected =
rateSummary.filter((rate) => rate !== null).length === totalRoomsRequired
const hasMemberRates = rateSummary.some(
(room) => "member" in room.product && room.product.member
(room) => room && "member" in room.product && room.product.member
)
const showMemberDiscountBanner = hasMemberRates && !isUserLoggedIn
@@ -134,12 +129,15 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
const isBookingCodeRate = rateSummary.some(
(rate) =>
rate &&
"public" in rate.product &&
rate.product.public?.rateType !== RateTypeEnum.Regular
)
const isVoucherRate = rateSummary.some((rate) => "voucher" in rate.product)
const isVoucherRate = rateSummary.some(
(rate) => rate && "voucher" in rate.product
)
const isCorporateChequeRate = rateSummary.some(
(rate) => "corporateCheque" in rate.product
(rate) => rate && "corporateCheque" in rate.product
)
const showDiscounted =
isUserLoggedIn ||
@@ -148,37 +146,29 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
isCorporateChequeRate
const mainRoomProduct = rateSummary[0]
let totalPriceToShow: Price
if ("redemption" in mainRoomProduct.product) {
// In case of reward night (redemption) only single room booking is supported by business rules
totalPriceToShow = calculateRedemptionTotalPrice(
mainRoomProduct.product.redemption
)
} else if ("voucher" in mainRoomProduct.product) {
totalPriceToShow = calculateVoucherPrice(rateSummary)
} else if ("corporateCheque" in mainRoomProduct.product) {
totalPriceToShow = calculateCorporateChequePrice(rateSummary)
} else {
totalPriceToShow = calculateTotalPrice(
rateSummary,
isUserLoggedIn,
petRoomPackage
)
const totalPriceToShow = getTotalPrice(
mainRoomProduct,
rateSummary,
isUserLoggedIn,
petRoomPackage
)
const rateProduct = rateSummary.find((rate) => rate?.product)?.product
if (!totalPriceToShow || !rateProduct) {
return null
}
let mainRoomCurrency = ""
if (
"member" in mainRoomProduct.product &&
mainRoomProduct.product.member?.localPrice
) {
mainRoomCurrency = mainRoomProduct.product.member.localPrice.currency
if ("member" in rateProduct && rateProduct.member?.localPrice) {
mainRoomCurrency = rateProduct.member.localPrice.currency
}
if (
!mainRoomCurrency &&
"public" in mainRoomProduct.product &&
mainRoomProduct.product.public?.localPrice
"public" in rateProduct &&
rateProduct.public?.localPrice
) {
mainRoomCurrency = mainRoomProduct.product.public.localPrice.currency
mainRoomCurrency = rateProduct.public.localPrice.currency
}
return (
@@ -186,38 +176,56 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
<div className={styles.summary}>
<div className={styles.content}>
<div className={styles.summaryText}>
{rateSummary.map((room, index) => (
<div key={index} className={styles.roomSummary}>
{rateSummary.length > 1 ? (
<>
<Subtitle color="uiTextHighContrast">
{rateSummary.map((room, index) => {
if (!room) {
return (
<div key={`unselected-${index}`}>
<Subtitle color="uiTextPlaceholder">
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: index + 1 }
)}
</Subtitle>
<Body color="uiTextMediumContrast">{room.roomType}</Body>
<Caption color="uiTextMediumContrast">
{getRateDetails(room.rate)}
</Caption>
</>
) : (
<>
<Subtitle color="uiTextHighContrast">
{room.roomType}
</Subtitle>
<Body color="uiTextMediumContrast">
{getRateDetails(room.rate)}
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Select room" })}
</Body>
</>
)}
</div>
))}
</div>
)
}
return (
<div key={index}>
{rateSummary.length > 1 ? (
<>
<Subtitle color="uiTextHighContrast">
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: index + 1 }
)}
</Subtitle>
<Body color="uiTextMediumContrast">{room.roomType}</Body>
<Caption color="uiTextMediumContrast">
{getRateDetails(room.rate)}
</Caption>
</>
) : (
<>
<Subtitle color="uiTextHighContrast">
{room.roomType}
</Subtitle>
<Body color="uiTextMediumContrast">
{getRateDetails(room.rate)}
</Body>
</>
)}
</div>
)
})}
{/* Render unselected rooms */}
{Array.from({
length: totalRoomsRequired - rateSummary.length,
}).map((_, index) => (
<div key={`unselected-${index}`} className={styles.roomSummary}>
<div key={`unselected-${index}`}>
<Subtitle color="uiTextPlaceholder">
{intl.formatMessage(
{ id: "Room {roomIndex}" },
@@ -235,47 +243,45 @@ export default function RateSummary({ isUserLoggedIn }: RateSummaryProps) {
<div className={styles.promoContainer}>
<SignupPromoDesktop
memberPrice={{
amount: rateSummary.reduce(
(
total,
{ features, packages: roomPackages, product }
) => {
const memberExists =
"member" in product && product.member
const publicExists =
"public" in product && product.public
if (!memberExists) {
if (!publicExists) {
return total
}
}
amount: rateSummary.reduce((total, rate) => {
if (!rate) {
return total
}
const price =
product.member?.localPrice.pricePerStay ||
product.public?.localPrice.pricePerStay
const { features, packages: roomPackages, product } = rate
if (!price) {
const memberExists = "member" in product && product.member
const publicExists = "public" in product && product.public
if (!memberExists) {
if (!publicExists) {
return total
}
}
const hasSelectedPetRoom = roomPackages.includes(
RoomPackageCodeEnum.PET_ROOM
)
if (!hasSelectedPetRoom) {
return total + price
}
const isPetRoom = features.find(
(feature) =>
feature.code === RoomPackageCodeEnum.PET_ROOM
)
const petRoomPrice =
isPetRoom && petRoomPackage
? Number(petRoomPackage.localPrice.totalPrice)
: 0
return total + price + petRoomPrice
},
0
),
const price =
product.member?.localPrice.pricePerStay ||
product.public?.localPrice.pricePerStay
if (!price) {
return total
}
const hasSelectedPetRoom = roomPackages.includes(
RoomPackageCodeEnum.PET_ROOM
)
if (!hasSelectedPetRoom) {
return total + price
}
const isPetRoom = features.find(
(feature) =>
feature.code === RoomPackageCodeEnum.PET_ROOM
)
const petRoomPrice =
isPetRoom && petRoomPackage
? Number(petRoomPackage.localPrice.totalPrice)
: 0
return total + price + petRoomPrice
}, 0),
currency: mainRoomCurrency,
}}
/>