fix: unite all price details modals to one and align on ui
This commit is contained in:
committed by
Michael Zetterberg
parent
8152aea649
commit
1f94c581ae
@@ -0,0 +1,92 @@
|
|||||||
|
"use client"
|
||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
||||||
|
|
||||||
|
import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal"
|
||||||
|
|
||||||
|
import { mapToPrice } from "./mapToPrice"
|
||||||
|
|
||||||
|
import type { Price } from "@/types/components/hotelReservation/price"
|
||||||
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
|
||||||
|
export default function PriceDetails() {
|
||||||
|
const { bookingCode, currency, fromDate, rooms, vat, toDate } =
|
||||||
|
useBookingConfirmationStore((state) => ({
|
||||||
|
bookingCode: state.bookingCode ?? undefined,
|
||||||
|
currency: state.currencyCode,
|
||||||
|
fromDate: state.fromDate,
|
||||||
|
rooms: state.rooms,
|
||||||
|
toDate: state.toDate,
|
||||||
|
vat: state.vat,
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (!rooms[0]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkInDate = dt(fromDate).format("YYYY-MM-DD")
|
||||||
|
const checkOutDate = dt(toDate).format("YYYY-MM-DD")
|
||||||
|
const nights = dt(toDate)
|
||||||
|
.startOf("day")
|
||||||
|
.diff(dt(fromDate).startOf("day"), "days")
|
||||||
|
|
||||||
|
const totalPrice = rooms.reduce<Price>(
|
||||||
|
(total, room) => {
|
||||||
|
if (!room) {
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkgsSum =
|
||||||
|
room.roomFeatures?.reduce((total, pkg) => total + pkg.totalPrice, 0) ??
|
||||||
|
0
|
||||||
|
|
||||||
|
if (room.cheques) {
|
||||||
|
// CorporateCheque Booking
|
||||||
|
total.local.currency = CurrencyEnum.CC
|
||||||
|
total.local.price = total.local.price + room.cheques
|
||||||
|
} else if (room.roomPoints) {
|
||||||
|
// Redemption Booking
|
||||||
|
total.local.currency = CurrencyEnum.POINTS
|
||||||
|
total.local.price = total.local.price + room.roomPoints
|
||||||
|
} else if (room.vouchers) {
|
||||||
|
// Vouchers Booking
|
||||||
|
total.local.currency = CurrencyEnum.Voucher
|
||||||
|
total.local.price = total.local.price + room.vouchers
|
||||||
|
} else {
|
||||||
|
// Price Booking
|
||||||
|
total.local.price = total.local.price + room.roomPrice + pkgsSum
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(room.cheques || room.roomPoints || room.vouchers) &&
|
||||||
|
room.roomPrice
|
||||||
|
) {
|
||||||
|
total.local.additionalPrice =
|
||||||
|
(total.local.additionalPrice || 0) + room.roomPrice + pkgsSum
|
||||||
|
total.local.additionalPriceCurrency = currency
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
},
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
currency,
|
||||||
|
price: 0,
|
||||||
|
},
|
||||||
|
requested: undefined,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const mappedRooms = mapToPrice(rooms, nights)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PriceDetailsModal
|
||||||
|
bookingCode={bookingCode}
|
||||||
|
fromDate={checkInDate}
|
||||||
|
rooms={mappedRooms}
|
||||||
|
toDate={checkOutDate}
|
||||||
|
totalPrice={totalPrice}
|
||||||
|
vat={vat}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
breakfastPackageSchema,
|
||||||
|
packageSchema,
|
||||||
|
} from "@/server/routers/hotels/schemas/packages"
|
||||||
|
|
||||||
|
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
|
||||||
|
import type { Package } from "@/types/requests/packages"
|
||||||
|
import type { Room } from "@/types/stores/booking-confirmation"
|
||||||
|
|
||||||
|
export function mapToPrice(rooms: (Room | null)[], nights: number) {
|
||||||
|
return rooms
|
||||||
|
.filter((room): room is Room => !!room)
|
||||||
|
.map((room) => {
|
||||||
|
let price
|
||||||
|
if (room.cheques) {
|
||||||
|
price = {
|
||||||
|
corporateCheque: {
|
||||||
|
additionalPricePerStay: room.roomPrice ? room.roomPrice : undefined,
|
||||||
|
currency: room.roomPrice ? room.currencyCode : undefined,
|
||||||
|
numberOfCheques: room.cheques,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if (room.roomPoints) {
|
||||||
|
price = {
|
||||||
|
redemption: {
|
||||||
|
additionalPricePerStay: room.roomPrice ? room.roomPrice : undefined,
|
||||||
|
currency: room.roomPrice ? room.currencyCode : undefined,
|
||||||
|
pointsPerNight: room.roomPoints / nights,
|
||||||
|
pointsPerStay: room.roomPoints,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if (room.vouchers) {
|
||||||
|
price = {
|
||||||
|
voucher: {
|
||||||
|
numberOfVouchers: room.vouchers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
price = {
|
||||||
|
regular: {
|
||||||
|
currency: room.currencyCode,
|
||||||
|
pricePerNight: room.roomPrice / nights,
|
||||||
|
pricePerStay: room.roomPrice,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const breakfastPackage = breakfastPackageSchema.safeParse({
|
||||||
|
code: room.breakfast?.code,
|
||||||
|
description: room.breakfast?.description,
|
||||||
|
localPrice: {
|
||||||
|
currency: room.breakfast?.currency,
|
||||||
|
price: room.breakfast?.unitPrice,
|
||||||
|
totalPrice: room.breakfast?.totalPrice,
|
||||||
|
},
|
||||||
|
packageType: room.breakfast?.type,
|
||||||
|
requestedPrice: {
|
||||||
|
currency: room.breakfast?.currency,
|
||||||
|
price: room.breakfast?.unitPrice,
|
||||||
|
totalPrice: room.breakfast?.totalPrice,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const packages = room.roomFeatures
|
||||||
|
?.map((featPkg) => {
|
||||||
|
const pkg = packageSchema.safeParse({
|
||||||
|
code: featPkg.code,
|
||||||
|
description: featPkg.description,
|
||||||
|
inventories: [],
|
||||||
|
localPrice: {
|
||||||
|
currency: featPkg.currency,
|
||||||
|
price: featPkg.unitPrice,
|
||||||
|
totalPrice: featPkg.totalPrice,
|
||||||
|
},
|
||||||
|
requestedPrice: {
|
||||||
|
currency: featPkg.currency,
|
||||||
|
price: featPkg.unitPrice,
|
||||||
|
totalPrice: featPkg.totalPrice,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (pkg.success) {
|
||||||
|
return pkg.data
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.filter((pkg): pkg is Package => !!pkg)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
adults: room.adults,
|
||||||
|
bedType: {
|
||||||
|
description: room.bedDescription,
|
||||||
|
roomTypeCode: room.roomTypeCode || "",
|
||||||
|
},
|
||||||
|
breakfast: breakfastPackage.success ? breakfastPackage.data : undefined,
|
||||||
|
breakfastIncluded: room.rateDefinition.breakfastIncluded,
|
||||||
|
childrenInRoom: room.childrenAges?.map((age) => ({
|
||||||
|
age,
|
||||||
|
bed: ChildBedMapEnum.UNKNOWN,
|
||||||
|
})),
|
||||||
|
packages,
|
||||||
|
price,
|
||||||
|
roomType: room.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import React from "react"
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
|
||||||
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
|
||||||
|
|
||||||
import Modal from "@/components/Modal"
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
|
||||||
import IconChip from "@/components/TempDesignSystem/IconChip"
|
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
||||||
import useLang from "@/hooks/useLang"
|
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
|
||||||
|
|
||||||
import styles from "./priceDetailsModal.module.css"
|
|
||||||
|
|
||||||
function Row({
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
bold,
|
|
||||||
}: {
|
|
||||||
label: string
|
|
||||||
value: string
|
|
||||||
bold?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<tr className={styles.row}>
|
|
||||||
<td>
|
|
||||||
<Caption type={bold ? "bold" : undefined}>{label}</Caption>
|
|
||||||
</td>
|
|
||||||
<td className={styles.price}>
|
|
||||||
<Caption type={bold ? "bold" : undefined}>{value}</Caption>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TableSection({ children }: React.PropsWithChildren) {
|
|
||||||
return <tbody className={styles.tableSection}>{children}</tbody>
|
|
||||||
}
|
|
||||||
|
|
||||||
function TableSectionHeader({
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
bold,
|
|
||||||
}: {
|
|
||||||
title: string
|
|
||||||
subtitle?: string
|
|
||||||
bold?: boolean
|
|
||||||
}) {
|
|
||||||
const typographyVariant = bold
|
|
||||||
? "Body/Paragraph/mdBold"
|
|
||||||
: "Body/Paragraph/mdRegular"
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th colSpan={2} align="left">
|
|
||||||
<Typography variant={typographyVariant}>
|
|
||||||
<p>{title}</p>
|
|
||||||
</Typography>
|
|
||||||
{subtitle ? (
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p>{subtitle}</p>
|
|
||||||
</Typography>
|
|
||||||
) : null}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PriceDetailsModal() {
|
|
||||||
const intl = useIntl()
|
|
||||||
const lang = useLang()
|
|
||||||
const {
|
|
||||||
rooms,
|
|
||||||
currencyCode,
|
|
||||||
vat,
|
|
||||||
fromDate,
|
|
||||||
toDate,
|
|
||||||
bookingCode,
|
|
||||||
isVatCurrency,
|
|
||||||
formattedTotalCost,
|
|
||||||
} = useBookingConfirmationStore((state) => ({
|
|
||||||
rooms: state.rooms,
|
|
||||||
currencyCode: state.currencyCode,
|
|
||||||
vat: state.vat,
|
|
||||||
fromDate: state.fromDate,
|
|
||||||
toDate: state.toDate,
|
|
||||||
bookingCode: state.bookingCode,
|
|
||||||
isVatCurrency: state.isVatCurrency,
|
|
||||||
formattedTotalCost: state.formattedTotalCost,
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (!rooms[0]) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkInDate = dt(fromDate).format("YYYY-MM-DD")
|
|
||||||
const checkOutDate = dt(toDate).format("YYYY-MM-DD")
|
|
||||||
|
|
||||||
const bookingTotal = rooms.reduce(
|
|
||||||
(acc, room) => {
|
|
||||||
if (room) {
|
|
||||||
return {
|
|
||||||
price: acc.price + room.totalPrice,
|
|
||||||
priceExVat: acc.priceExVat + room.totalPriceExVat,
|
|
||||||
vatAmount: acc.vatAmount + room.vatAmount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
},
|
|
||||||
{ price: 0, priceExVat: 0, vatAmount: 0 }
|
|
||||||
)
|
|
||||||
|
|
||||||
const diff = dt(checkOutDate).diff(checkInDate, "days")
|
|
||||||
const nights = intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "{totalNights, plural, one {# night} other {# nights}}",
|
|
||||||
},
|
|
||||||
{ totalNights: diff }
|
|
||||||
)
|
|
||||||
|
|
||||||
const duration = ` ${dt(fromDate).locale(lang).format("ddd, D MMM")}
|
|
||||||
-
|
|
||||||
${dt(toDate).locale(lang).format("ddd, D MMM")} (${nights})`
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Price details",
|
|
||||||
})}
|
|
||||||
trigger={
|
|
||||||
<Button intent="text">
|
|
||||||
<Caption color="burgundy">
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Price details",
|
|
||||||
})}
|
|
||||||
</Caption>
|
|
||||||
<MaterialIcon icon="chevron_right" color="CurrentColor" size={20} />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<table className={styles.priceDetailsTable}>
|
|
||||||
{rooms.map((room, idx) => {
|
|
||||||
return room ? (
|
|
||||||
<React.Fragment key={idx}>
|
|
||||||
<TableSection>
|
|
||||||
{rooms.length > 1 && (
|
|
||||||
<TableSectionHeader
|
|
||||||
title={intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "Room {roomIndex}",
|
|
||||||
},
|
|
||||||
{ roomIndex: idx + 1 }
|
|
||||||
)}
|
|
||||||
bold
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<TableSectionHeader title={room.name} subtitle={duration} />
|
|
||||||
{room.roomFeatures
|
|
||||||
? room.roomFeatures.map((feature) => (
|
|
||||||
<Row
|
|
||||||
key={feature.code}
|
|
||||||
label={feature.description}
|
|
||||||
value={formatPrice(
|
|
||||||
intl,
|
|
||||||
feature.totalPrice,
|
|
||||||
currencyCode
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
{room.bedDescription ? (
|
|
||||||
<Row
|
|
||||||
label={room.bedDescription}
|
|
||||||
value={formatPrice(intl, 0, currencyCode)}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Row
|
|
||||||
bold
|
|
||||||
label={intl.formatMessage({
|
|
||||||
defaultMessage: "Room charge",
|
|
||||||
})}
|
|
||||||
value={room.formattedRoomCost}
|
|
||||||
/>
|
|
||||||
</TableSection>
|
|
||||||
|
|
||||||
{room.breakfast ? (
|
|
||||||
<TableSection>
|
|
||||||
<Row
|
|
||||||
label={intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Breakfast ({totalAdults, plural, one {# adult} other {# adults}}) x {totalBreakfasts}",
|
|
||||||
},
|
|
||||||
{ totalAdults: room.adults, totalBreakfasts: diff }
|
|
||||||
)}
|
|
||||||
value={formatPrice(
|
|
||||||
intl,
|
|
||||||
room.breakfast.unitPrice * room.adults,
|
|
||||||
currencyCode
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{room.childrenAges?.length ? (
|
|
||||||
<Row
|
|
||||||
label={intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
totalChildren: room.childrenAges.length,
|
|
||||||
totalBreakfasts: diff,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
value={formatPrice(intl, 0, currencyCode)}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Row
|
|
||||||
bold
|
|
||||||
label={intl.formatMessage({
|
|
||||||
defaultMessage: "Breakfast charge",
|
|
||||||
})}
|
|
||||||
value={formatPrice(
|
|
||||||
intl,
|
|
||||||
room.breakfast.totalPrice,
|
|
||||||
currencyCode
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TableSection>
|
|
||||||
) : null}
|
|
||||||
</React.Fragment>
|
|
||||||
) : null
|
|
||||||
})}
|
|
||||||
<TableSection>
|
|
||||||
<TableSectionHeader
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Total",
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{isVatCurrency ? (
|
|
||||||
<>
|
|
||||||
<Row
|
|
||||||
label={intl.formatMessage({
|
|
||||||
defaultMessage: "Price excluding VAT",
|
|
||||||
})}
|
|
||||||
value={formatPrice(intl, bookingTotal.priceExVat, currencyCode)}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage: "VAT {vat}%",
|
|
||||||
},
|
|
||||||
{ vat }
|
|
||||||
)}
|
|
||||||
value={formatPrice(intl, bookingTotal.vatAmount, currencyCode)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
<tr className={styles.row}>
|
|
||||||
<td>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Price including VAT",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</Typography>
|
|
||||||
</td>
|
|
||||||
<td className={styles.price}>
|
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
|
||||||
<span>{formattedTotalCost}</span>
|
|
||||||
</Typography>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{bookingCode && (
|
|
||||||
<tr className={styles.row}>
|
|
||||||
<td colSpan={2} align="left">
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
||||||
<IconChip
|
|
||||||
color="blue"
|
|
||||||
icon={<DiscountIcon color="Icon/Feedback/Information" />}
|
|
||||||
>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
"<strong>Booking code</strong>: {value}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: bookingCode,
|
|
||||||
strong: (text) => (
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
<strong>{text}</strong>
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</IconChip>
|
|
||||||
</Typography>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</TableSection>
|
|
||||||
</table>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
.priceDetailsTable {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
text-align: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:has(tr > th) {
|
|
||||||
padding-top: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:has(tr > th):not(:first-of-type) {
|
|
||||||
border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:not(:last-child) {
|
|
||||||
padding-bottom: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.priceDetailsTable {
|
|
||||||
min-width: 512px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
|
|||||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
|
|
||||||
import PriceDetailsModal from "../../PriceDetailsModal"
|
import PriceDetails from "../../PriceDetails"
|
||||||
|
|
||||||
import styles from "./totalPrice.module.css"
|
import styles from "./totalPrice.module.css"
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ export default function TotalPrice() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{hasAllRoomsLoaded ? (
|
{hasAllRoomsLoaded ? (
|
||||||
<PriceDetailsModal />
|
<PriceDetails />
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.priceDetailsLoader}>
|
<div className={styles.priceDetailsLoader}>
|
||||||
<SkeletonShimmer width={"100%"} />
|
<SkeletonShimmer width={"100%"} />
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
.priceDetailsTable {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
text-align: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:has(tr > th) {
|
|
||||||
padding-top: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:has(tr > th):not(:first-of-type) {
|
|
||||||
border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:not(:last-child) {
|
|
||||||
padding-bottom: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.priceDetailsTable {
|
|
||||||
min-width: 512px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import { formatPrice } from "@/utils/numberFormatting"
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
import PriceDetailsTable from "./PriceDetailsTable"
|
import { mapToPrice } from "./mapToPrice"
|
||||||
|
|
||||||
import styles from "./ui.module.css"
|
import styles from "./ui.module.css"
|
||||||
|
|
||||||
@@ -95,6 +95,8 @@ export default function SummaryUI({
|
|||||||
? totalPrice.requested.currency === totalPrice.local.currency
|
? totalPrice.requested.currency === totalPrice.local.currency
|
||||||
: false
|
: false
|
||||||
|
|
||||||
|
const priceDetailsRooms = mapToPrice(rooms, isMember)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.summary}>
|
<section className={styles.summary}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
@@ -440,17 +442,14 @@ export default function SummaryUI({
|
|||||||
{ b: (str) => <b>{str}</b> }
|
{ b: (str) => <b>{str}</b> }
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<PriceDetailsModal>
|
<PriceDetailsModal
|
||||||
<PriceDetailsTable
|
bookingCode={booking.bookingCode}
|
||||||
bookingCode={booking.bookingCode}
|
fromDate={booking.fromDate}
|
||||||
fromDate={booking.fromDate}
|
rooms={priceDetailsRooms}
|
||||||
isMember={isMember}
|
toDate={booking.toDate}
|
||||||
rooms={rooms.map((r) => r.room)}
|
totalPrice={totalPrice}
|
||||||
toDate={booking.toDate}
|
vat={vat}
|
||||||
totalPrice={totalPrice}
|
/>
|
||||||
vat={vat}
|
|
||||||
/>
|
|
||||||
</PriceDetailsModal>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Body textTransform="bold" data-testid="total-price">
|
<Body textTransform="bold" data-testid="total-price">
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import type { RoomState } from "@/types/stores/enter-details"
|
||||||
|
|
||||||
|
export function mapToPrice(rooms: RoomState[], isMember: boolean) {
|
||||||
|
return rooms
|
||||||
|
.filter((room) => room && room.room.roomRate)
|
||||||
|
.map(({ room }, idx) => {
|
||||||
|
const isMainRoom = idx === 0
|
||||||
|
|
||||||
|
if ("corporateCheque" in room.roomRate) {
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
packages: room.roomFeatures,
|
||||||
|
price: {
|
||||||
|
corporateCheque: room.roomRate.corporateCheque.localPrice,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("redemption" in room.roomRate) {
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
packages: room.roomFeatures,
|
||||||
|
price: {
|
||||||
|
redemption: room.roomRate.redemption.localPrice,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("voucher" in room.roomRate) {
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
packages: room.roomFeatures,
|
||||||
|
price: {
|
||||||
|
voucher: room.roomRate.voucher,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMemberRate = !!(room.guest.join || room.guest.membershipNo)
|
||||||
|
if ((isMember && isMainRoom) || isMemberRate) {
|
||||||
|
if ("member" in room.roomRate && room.roomRate.member) {
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
packages: room.roomFeatures,
|
||||||
|
price: {
|
||||||
|
regular: room.roomRate.member.localPrice,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("public" in room.roomRate && room.roomRate.public) {
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
packages: room.roomFeatures,
|
||||||
|
price: {
|
||||||
|
regular: room.roomRate.public.localPrice,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(room.roomRate)
|
||||||
|
throw new Error(`Unknown roomRate`)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
.priceDetailsTable {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
text-align: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:has(tr > th) {
|
|
||||||
padding-top: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:has(tr > th):not(:first-of-type) {
|
|
||||||
border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:not(:last-child) {
|
|
||||||
padding-bottom: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.priceDetailsTable {
|
|
||||||
min-width: 512px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
|
import { useMyStayRoomDetailsStore } from "@/stores/my-stay/myStayRoomDetailsStore"
|
||||||
import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
|
|
||||||
|
|
||||||
import PriceDetailsModal from "../../PriceDetailsModal"
|
import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal"
|
||||||
import PriceDetailsTable from "./PriceDetailsTable"
|
|
||||||
|
import { calculateTotalPrice, mapToPrice } from "./mapToPrice"
|
||||||
|
|
||||||
import styles from "./priceDetails.module.css"
|
import styles from "./priceDetails.module.css"
|
||||||
|
|
||||||
@@ -13,27 +13,32 @@ export default function PriceDetails() {
|
|||||||
const linkedReservationRooms = useMyStayRoomDetailsStore(
|
const linkedReservationRooms = useMyStayRoomDetailsStore(
|
||||||
(state) => state.linkedReservationRooms
|
(state) => state.linkedReservationRooms
|
||||||
)
|
)
|
||||||
const currencyCode = useMyStayTotalPriceStore((state) => state.currencyCode)
|
|
||||||
const totalPrice = useMyStayTotalPriceStore((state) => state.totalPrice)
|
|
||||||
|
|
||||||
|
const rooms = [bookedRoom, ...linkedReservationRooms]
|
||||||
|
.filter((room) => !room.isCancelled)
|
||||||
|
.map((room) => ({
|
||||||
|
...room,
|
||||||
|
breakfastIncluded: room.rateDefinition.breakfastIncluded,
|
||||||
|
price: mapToPrice(room),
|
||||||
|
roomType: room.roomName,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const bookingCode =
|
||||||
|
rooms.find((room) => room.bookingCode)?.bookingCode ?? undefined
|
||||||
|
const totalPrice = calculateTotalPrice(rooms, bookedRoom.currencyCode)
|
||||||
|
|
||||||
|
const fromDate = dt(bookedRoom.checkInDate).format("YYYY-MM-DD")
|
||||||
|
const toDate = dt(bookedRoom.checkOutDate).format("YYYY-MM-DD")
|
||||||
return (
|
return (
|
||||||
<div className={styles.priceDetailsModal}>
|
<div className={styles.priceDetailsModal}>
|
||||||
<PriceDetailsModal>
|
<PriceDetailsModal
|
||||||
<PriceDetailsTable
|
bookingCode={bookingCode}
|
||||||
fromDate={dt(bookedRoom.checkInDate).format("YYYY-MM-DD")}
|
fromDate={fromDate}
|
||||||
toDate={dt(bookedRoom.checkOutDate).format("YYYY-MM-DD")}
|
rooms={rooms}
|
||||||
linkedReservationRooms={linkedReservationRooms}
|
toDate={toDate}
|
||||||
bookedRoom={bookedRoom}
|
totalPrice={totalPrice}
|
||||||
totalPrice={{
|
vat={bookedRoom.vatPercentage}
|
||||||
requested: undefined,
|
/>
|
||||||
local: {
|
|
||||||
currency: currencyCode,
|
|
||||||
price: totalPrice ?? 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
vat={bookedRoom.vatPercentage}
|
|
||||||
/>
|
|
||||||
</PriceDetailsModal>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
|
import { sumPackages } from "@/components/HotelReservation/utils"
|
||||||
|
|
||||||
|
import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
|
||||||
|
import type { Price } from "@/types/components/hotelReservation/price"
|
||||||
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
import type { Room } from "@/stores/my-stay/myStayRoomDetailsStore"
|
||||||
|
|
||||||
|
export function mapToPrice(room: Room) {
|
||||||
|
switch (room.priceType) {
|
||||||
|
case PriceTypeEnum.cheque:
|
||||||
|
return {
|
||||||
|
corporateCheque: {
|
||||||
|
additionalPricePerStay: room.roomPrice.perStay.local.price,
|
||||||
|
currency: room.roomPrice.perStay.local.currency,
|
||||||
|
numberOfCheques: room.cheques,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case PriceTypeEnum.money:
|
||||||
|
return {
|
||||||
|
regular: {
|
||||||
|
currency: room.currencyCode,
|
||||||
|
pricePerNight: room.roomPrice.perNight,
|
||||||
|
pricePerStay: room.roomPrice.perStay,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case PriceTypeEnum.points:
|
||||||
|
const nights = dt(room.checkOutDate)
|
||||||
|
.startOf("day")
|
||||||
|
.diff(dt(room.checkInDate).startOf("day"), "days")
|
||||||
|
return {
|
||||||
|
redemption: {
|
||||||
|
additionalPricePerStay: room.roomPrice.perStay.local.price,
|
||||||
|
currency: room.roomPrice.perStay.local.currency,
|
||||||
|
pointsPerNight: room.roomPoints / nights,
|
||||||
|
pointsPerStay: room.roomPoints,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case PriceTypeEnum.voucher:
|
||||||
|
return {
|
||||||
|
voucher: {
|
||||||
|
numberOfVouchers: room.vouchers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown payment method!`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
|
||||||
|
return rooms.reduce<Price>(
|
||||||
|
(total, room) => {
|
||||||
|
const pkgsSum = sumPackages(room.packages)
|
||||||
|
let breakfastPrice = 0
|
||||||
|
if (room.breakfast && !room.rateDefinition.breakfastIncluded) {
|
||||||
|
breakfastPrice = room.breakfast.localPrice.totalPrice
|
||||||
|
}
|
||||||
|
switch (room.priceType) {
|
||||||
|
case PriceTypeEnum.cheque:
|
||||||
|
{
|
||||||
|
total.local.currency = CurrencyEnum.CC
|
||||||
|
total.local.price = total.local.price + room.cheques
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case PriceTypeEnum.money:
|
||||||
|
{
|
||||||
|
total.local.price =
|
||||||
|
total.local.price +
|
||||||
|
room.roomPrice.perStay.local.price +
|
||||||
|
pkgsSum.price +
|
||||||
|
breakfastPrice
|
||||||
|
|
||||||
|
if (!total.local.currency) {
|
||||||
|
total.local.currency = room.currencyCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case PriceTypeEnum.points:
|
||||||
|
{
|
||||||
|
total.local.currency = CurrencyEnum.POINTS
|
||||||
|
total.local.price = total.local.price + room.roomPoints
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case PriceTypeEnum.voucher:
|
||||||
|
total.local.currency = CurrencyEnum.Voucher
|
||||||
|
total.local.price = total.local.price + room.vouchers
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (room.priceType) {
|
||||||
|
case PriceTypeEnum.cheque:
|
||||||
|
case PriceTypeEnum.points:
|
||||||
|
case PriceTypeEnum.voucher:
|
||||||
|
{
|
||||||
|
if (room.roomPrice.perStay.local.price || pkgsSum) {
|
||||||
|
total.local.additionalPrice =
|
||||||
|
room.roomPrice.perStay.local.price +
|
||||||
|
pkgsSum.price +
|
||||||
|
breakfastPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!total.local.additionalPriceCurrency) {
|
||||||
|
if (room.roomPrice.perStay.local.currency) {
|
||||||
|
total.local.additionalPriceCurrency =
|
||||||
|
room.roomPrice.perStay.local.currency
|
||||||
|
} else {
|
||||||
|
total.local.additionalPriceCurrency = currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
},
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
currency,
|
||||||
|
price: 0,
|
||||||
|
},
|
||||||
|
requested: undefined,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import { Button } from "@scandic-hotels/design-system/Button"
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
||||||
|
|
||||||
import Modal from "@/components/Modal"
|
|
||||||
|
|
||||||
export default function PriceDetailsModal({
|
|
||||||
children,
|
|
||||||
}: React.PropsWithChildren) {
|
|
||||||
const intl = useIntl()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Price details",
|
|
||||||
})}
|
|
||||||
trigger={
|
|
||||||
<Button
|
|
||||||
variant="Text"
|
|
||||||
typography="Body/Supporting text (caption)/smBold"
|
|
||||||
wrapping={false}
|
|
||||||
>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Price details",
|
|
||||||
})}
|
|
||||||
<MaterialIcon icon="chevron_right" color="CurrentColor" size={20} />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import BoldRow from "./Row/Bold"
|
||||||
|
import RegularRow from "./Row/Regular"
|
||||||
|
import Tbody from "./Tbody"
|
||||||
|
|
||||||
|
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
|
||||||
|
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
|
|
||||||
|
interface BreakfastProps {
|
||||||
|
adults: number
|
||||||
|
breakfast: BreakfastPackage | false | undefined | null
|
||||||
|
breakfastIncluded: boolean
|
||||||
|
childrenInRoom: Child[] | undefined
|
||||||
|
currency: string
|
||||||
|
nights: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Breakfast({
|
||||||
|
adults,
|
||||||
|
breakfast,
|
||||||
|
breakfastIncluded,
|
||||||
|
childrenInRoom,
|
||||||
|
currency,
|
||||||
|
nights,
|
||||||
|
}: BreakfastProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (breakfastIncluded) {
|
||||||
|
const included = intl.formatMessage({ defaultMessage: "Included" })
|
||||||
|
return (
|
||||||
|
<Tbody border>
|
||||||
|
<RegularRow
|
||||||
|
label={intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Breakfast ({totalAdults, plural, one {# adult} other {# adults}}) x {totalBreakfasts}",
|
||||||
|
},
|
||||||
|
{ totalAdults: adults, totalBreakfasts: nights }
|
||||||
|
)}
|
||||||
|
value={included}
|
||||||
|
/>
|
||||||
|
{childrenInRoom?.length ? (
|
||||||
|
<RegularRow
|
||||||
|
label={intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
totalChildren: childrenInRoom.length,
|
||||||
|
totalBreakfasts: nights,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
value={included}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<BoldRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "Breakfast charge" })}
|
||||||
|
value={formatPrice(intl, 0, currency)}
|
||||||
|
/>
|
||||||
|
</Tbody>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!breakfast) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const breakfastAdultsPricePerNight = formatPrice(
|
||||||
|
intl,
|
||||||
|
breakfast.localPrice.price * adults,
|
||||||
|
breakfast.localPrice.currency
|
||||||
|
)
|
||||||
|
const breakfastAdultsTotalPrice = formatPrice(
|
||||||
|
intl,
|
||||||
|
breakfast.localPrice.price * adults * nights,
|
||||||
|
breakfast.localPrice.currency
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tbody border>
|
||||||
|
<RegularRow
|
||||||
|
label={intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Breakfast ({totalAdults, plural, one {# adult} other {# adults}}) x {totalBreakfasts}",
|
||||||
|
},
|
||||||
|
{ totalAdults: adults, totalBreakfasts: nights }
|
||||||
|
)}
|
||||||
|
value={breakfastAdultsPricePerNight}
|
||||||
|
/>
|
||||||
|
{childrenInRoom?.length ? (
|
||||||
|
<RegularRow
|
||||||
|
label={intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
totalChildren: childrenInRoom.length,
|
||||||
|
totalBreakfasts: nights,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
value={formatPrice(intl, 0, breakfast.localPrice.currency)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<BoldRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "Breakfast charge" })}
|
||||||
|
value={breakfastAdultsTotalPrice}
|
||||||
|
/>
|
||||||
|
</Tbody>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import styles from "./row.module.css"
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BoldRow({ label, value }: RowProps) {
|
||||||
|
return (
|
||||||
|
<tr className={styles.row}>
|
||||||
|
<td>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
|
<span>{label}</span>
|
||||||
|
</Typography>
|
||||||
|
</td>
|
||||||
|
<td className={styles.price}>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
|
<span>{value}</span>
|
||||||
|
</Typography>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import IconChip from "@/components/TempDesignSystem/IconChip"
|
||||||
|
|
||||||
|
import styles from "./row.module.css"
|
||||||
|
|
||||||
|
interface BookingCodeRowProps {
|
||||||
|
bookingCode?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BookingCodeRow({ bookingCode }: BookingCodeRowProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (!bookingCode) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = intl.formatMessage(
|
||||||
|
{ defaultMessage: "<strong>Booking code</strong>: {value}" },
|
||||||
|
{
|
||||||
|
value: bookingCode,
|
||||||
|
strong: (text) => (
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
|
<strong>{text}</strong>
|
||||||
|
</Typography>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={styles.row}>
|
||||||
|
<td colSpan={2} align="left">
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<IconChip
|
||||||
|
color="blue"
|
||||||
|
icon={<DiscountIcon color="Icon/Feedback/Information" />}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</IconChip>
|
||||||
|
</Typography>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import styles from "./row.module.css"
|
||||||
|
|
||||||
|
import type { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
import type { Package } from "@/types/requests/packages"
|
||||||
|
|
||||||
|
interface DiscountedRegularPriceRowProps {
|
||||||
|
currency: CurrencyEnum
|
||||||
|
packages: Package[]
|
||||||
|
regularPrice?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DiscountedRegularPriceRow({
|
||||||
|
currency,
|
||||||
|
packages,
|
||||||
|
regularPrice,
|
||||||
|
}: DiscountedRegularPriceRowProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (!regularPrice) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalPackagesPrice = packages.reduce(
|
||||||
|
(total, pkg) => total + pkg.localPrice.totalPrice,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
const price = formatPrice(intl, regularPrice + totalPackagesPrice, currency)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={styles.row}>
|
||||||
|
<td></td>
|
||||||
|
<td className={styles.price}>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<span>
|
||||||
|
<s>{price}</s>
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
<Caption color="uiTextMediumContrast" striked></Caption>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
interface TrProps {
|
||||||
|
subtitle?: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HeaderRow({ subtitle, title }: TrProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<tr>
|
||||||
|
<th colSpan={2}>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<span>{title}</span>
|
||||||
|
</Typography>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{subtitle ? (
|
||||||
|
<tr>
|
||||||
|
<th colSpan={2}>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<span>{subtitle}</span>
|
||||||
|
</Typography>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import styles from "./row.module.css"
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LargeRow({ label, value }: RowProps) {
|
||||||
|
return (
|
||||||
|
<tr className={styles.row}>
|
||||||
|
<td>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<span>{label}</span>
|
||||||
|
</Typography>
|
||||||
|
</td>
|
||||||
|
<td className={styles.price}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<span>{value}</span>
|
||||||
|
</Typography>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import RegularRow from "../Regular"
|
||||||
|
|
||||||
|
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||||
|
|
||||||
|
interface BedTypeRowProps {
|
||||||
|
bedType: BedTypeSchema | undefined
|
||||||
|
currency?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BedTypeRow({
|
||||||
|
bedType,
|
||||||
|
currency = "",
|
||||||
|
}: BedTypeRowProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (!bedType) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RegularRow
|
||||||
|
label={bedType.description}
|
||||||
|
value={formatPrice(intl, 0, currency)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { sumPackages } from "@/components/HotelReservation/utils"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import BoldRow from "../Bold"
|
||||||
|
import RegularRow from "../Regular"
|
||||||
|
import BedTypeRow from "./BedType"
|
||||||
|
import PackagesRow from "./Packages"
|
||||||
|
|
||||||
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
import type { SharedPriceRowProps } from "./price"
|
||||||
|
|
||||||
|
export interface CorporateChequePriceType {
|
||||||
|
corporateCheque?: {
|
||||||
|
additionalPricePerStay?: number
|
||||||
|
currency?: CurrencyEnum
|
||||||
|
numberOfCheques: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CorporateChequePriceProps extends SharedPriceRowProps {
|
||||||
|
currency: string
|
||||||
|
nights: number
|
||||||
|
price: CorporateChequePriceType["corporateCheque"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CorporateChequePrice({
|
||||||
|
bedType,
|
||||||
|
currency,
|
||||||
|
nights,
|
||||||
|
packages,
|
||||||
|
price,
|
||||||
|
}: CorporateChequePriceProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (!price) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const averagePriceTitle = intl.formatMessage({
|
||||||
|
defaultMessage: "Average price per night",
|
||||||
|
})
|
||||||
|
|
||||||
|
const pkgsSum = sumPackages(packages)
|
||||||
|
const roomAdditionalPrice = price.additionalPricePerStay
|
||||||
|
let additionalPricePerStay
|
||||||
|
if (roomAdditionalPrice) {
|
||||||
|
additionalPricePerStay = roomAdditionalPrice + pkgsSum.price
|
||||||
|
} else if (pkgsSum.price) {
|
||||||
|
additionalPricePerStay = pkgsSum.price
|
||||||
|
}
|
||||||
|
|
||||||
|
const averageChequesPerNight = price.numberOfCheques / nights
|
||||||
|
const averageAdditionalPricePerNight = roomAdditionalPrice
|
||||||
|
? Math.ceil(roomAdditionalPrice / nights)
|
||||||
|
: null
|
||||||
|
|
||||||
|
const additionalCurrency = price.currency ?? pkgsSum.currency
|
||||||
|
let averagePricePerNight = `${averageChequesPerNight} ${CurrencyEnum.CC}`
|
||||||
|
if (averageAdditionalPricePerNight) {
|
||||||
|
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${additionalCurrency}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RegularRow label={averagePriceTitle} value={averagePricePerNight} />
|
||||||
|
<BedTypeRow bedType={bedType} currency={currency} />
|
||||||
|
<PackagesRow packages={packages} />
|
||||||
|
<BoldRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "Room charge" })}
|
||||||
|
value={formatPrice(
|
||||||
|
intl,
|
||||||
|
price.numberOfCheques,
|
||||||
|
CurrencyEnum.CC,
|
||||||
|
additionalPricePerStay,
|
||||||
|
additionalCurrency
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import RegularRow from "../Regular"
|
||||||
|
|
||||||
|
import type { Packages as PackagesType } from "@/types/requests/packages"
|
||||||
|
|
||||||
|
interface PackagesProps {
|
||||||
|
packages: PackagesType | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PackagesRow({ packages }: PackagesProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (!packages || !packages.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return packages?.map((pkg) => (
|
||||||
|
<RegularRow
|
||||||
|
key={pkg.code}
|
||||||
|
label={pkg.description}
|
||||||
|
value={formatPrice(
|
||||||
|
intl,
|
||||||
|
+pkg.localPrice.totalPrice,
|
||||||
|
pkg.localPrice.currency
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { sumPackages } from "@/components/HotelReservation/utils"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import BoldRow from "../Bold"
|
||||||
|
import RegularRow from "../Regular"
|
||||||
|
import BedTypeRow from "./BedType"
|
||||||
|
import PackagesRow from "./Packages"
|
||||||
|
|
||||||
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
import type { SharedPriceRowProps } from "./price"
|
||||||
|
|
||||||
|
export interface RedemptionPriceType {
|
||||||
|
redemption?: {
|
||||||
|
additionalPricePerStay?: number
|
||||||
|
currency?: CurrencyEnum
|
||||||
|
pointsPerNight: number
|
||||||
|
pointsPerStay: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RedemptionPriceProps extends SharedPriceRowProps {
|
||||||
|
currency: string
|
||||||
|
nights: number
|
||||||
|
price: RedemptionPriceType["redemption"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RedemptionPrice({
|
||||||
|
bedType,
|
||||||
|
currency,
|
||||||
|
nights,
|
||||||
|
packages,
|
||||||
|
price,
|
||||||
|
}: RedemptionPriceProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (!price) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const averagePriceTitle = intl.formatMessage({
|
||||||
|
defaultMessage: "Average price per night",
|
||||||
|
})
|
||||||
|
const pkgsSum = sumPackages(packages)
|
||||||
|
|
||||||
|
const roomAdditionalPrice = price.additionalPricePerStay
|
||||||
|
let additionalPricePerStay
|
||||||
|
if (roomAdditionalPrice) {
|
||||||
|
additionalPricePerStay = roomAdditionalPrice + pkgsSum.price
|
||||||
|
} else if (pkgsSum.price) {
|
||||||
|
additionalPricePerStay = pkgsSum.price
|
||||||
|
}
|
||||||
|
|
||||||
|
const averageAdditionalPricePerNight = roomAdditionalPrice
|
||||||
|
? Math.ceil(roomAdditionalPrice / nights)
|
||||||
|
: null
|
||||||
|
|
||||||
|
const additionalCurrency = price.currency ?? pkgsSum.currency
|
||||||
|
let averagePricePerNight = `${price.pointsPerNight} ${CurrencyEnum.POINTS}`
|
||||||
|
if (averageAdditionalPricePerNight) {
|
||||||
|
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${additionalCurrency}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RegularRow label={averagePriceTitle} value={averagePricePerNight} />
|
||||||
|
<BedTypeRow bedType={bedType} currency={currency} />
|
||||||
|
<PackagesRow packages={packages} />
|
||||||
|
<BoldRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "Room charge" })}
|
||||||
|
value={formatPrice(
|
||||||
|
intl,
|
||||||
|
price.pointsPerStay,
|
||||||
|
CurrencyEnum.POINTS,
|
||||||
|
additionalPricePerStay,
|
||||||
|
additionalCurrency
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { sumPackages } from "@/components/HotelReservation/utils"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import BoldRow from "../Bold"
|
||||||
|
import RegularRow from "../Regular"
|
||||||
|
import BedTypeRow from "./BedType"
|
||||||
|
import PackagesRow from "./Packages"
|
||||||
|
|
||||||
|
import type { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
import type { SharedPriceRowProps } from "./price"
|
||||||
|
|
||||||
|
export interface RegularPriceType {
|
||||||
|
regular?: {
|
||||||
|
currency: CurrencyEnum
|
||||||
|
pricePerNight: number
|
||||||
|
pricePerStay: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegularPriceProps extends SharedPriceRowProps {
|
||||||
|
price: RegularPriceType["regular"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RegularPrice({
|
||||||
|
bedType,
|
||||||
|
packages,
|
||||||
|
price,
|
||||||
|
}: RegularPriceProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (!price) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const averagePriceTitle = intl.formatMessage({
|
||||||
|
defaultMessage: "Average price per night",
|
||||||
|
})
|
||||||
|
|
||||||
|
const avgeragePricePerNight = formatPrice(
|
||||||
|
intl,
|
||||||
|
price.pricePerNight,
|
||||||
|
price.currency
|
||||||
|
)
|
||||||
|
|
||||||
|
const pkgs = sumPackages(packages)
|
||||||
|
|
||||||
|
const roomCharge = formatPrice(
|
||||||
|
intl,
|
||||||
|
price.pricePerStay + pkgs.price,
|
||||||
|
price.currency
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RegularRow label={averagePriceTitle} value={avgeragePricePerNight} />
|
||||||
|
<BedTypeRow bedType={bedType} currency={price.currency} />
|
||||||
|
<PackagesRow packages={packages} />
|
||||||
|
<BoldRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "Room charge" })}
|
||||||
|
value={roomCharge}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { sumPackages } from "@/components/HotelReservation/utils"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import BoldRow from "../Bold"
|
||||||
|
import RegularRow from "../Regular"
|
||||||
|
import BedTypeRow from "./BedType"
|
||||||
|
import PackagesRow from "./Packages"
|
||||||
|
|
||||||
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
import type { SharedPriceRowProps } from "./price"
|
||||||
|
|
||||||
|
export interface VoucherPriceType {
|
||||||
|
voucher?: {
|
||||||
|
numberOfVouchers: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VoucherPriceProps extends SharedPriceRowProps {
|
||||||
|
currency: string
|
||||||
|
nights: number
|
||||||
|
price: VoucherPriceType["voucher"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VoucherPrice({
|
||||||
|
bedType,
|
||||||
|
currency,
|
||||||
|
nights,
|
||||||
|
packages,
|
||||||
|
price,
|
||||||
|
}: VoucherPriceProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (!price) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const averagePriceTitle = intl.formatMessage({
|
||||||
|
defaultMessage: "Average price per night",
|
||||||
|
})
|
||||||
|
const pkgsSum = sumPackages(packages)
|
||||||
|
|
||||||
|
let additionalPricePerStay
|
||||||
|
if (pkgsSum.price) {
|
||||||
|
additionalPricePerStay = pkgsSum.price
|
||||||
|
}
|
||||||
|
|
||||||
|
const averageAdditionalPricePerNight = additionalPricePerStay
|
||||||
|
? Math.ceil(additionalPricePerStay / nights)
|
||||||
|
: null
|
||||||
|
|
||||||
|
let averagePricePerNight = `${price.numberOfVouchers} ${CurrencyEnum.Voucher}`
|
||||||
|
if (averageAdditionalPricePerNight) {
|
||||||
|
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${pkgsSum.currency}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RegularRow label={averagePriceTitle} value={averagePricePerNight} />
|
||||||
|
<BedTypeRow bedType={bedType} currency={currency} />
|
||||||
|
<PackagesRow packages={packages} />
|
||||||
|
<BoldRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "Room charge" })}
|
||||||
|
value={formatPrice(
|
||||||
|
intl,
|
||||||
|
price.numberOfVouchers,
|
||||||
|
CurrencyEnum.Voucher,
|
||||||
|
additionalPricePerStay,
|
||||||
|
pkgsSum.currency
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||||
|
import type { Packages } from "@/types/requests/packages"
|
||||||
|
|
||||||
|
export interface SharedPriceRowProps {
|
||||||
|
bedType: BedTypeSchema | undefined
|
||||||
|
packages: Packages | null
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import styles from "./row.module.css"
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RegularRow({ label, value }: RowProps) {
|
||||||
|
return (
|
||||||
|
<tr className={styles.row}>
|
||||||
|
<td>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<span>{label}</span>
|
||||||
|
</Typography>
|
||||||
|
</td>
|
||||||
|
<td className={styles.price}>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<span>{value}</span>
|
||||||
|
</Typography>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import RegularRow from "./Regular"
|
||||||
|
|
||||||
|
import type { Price } from "@/types/components/hotelReservation/price"
|
||||||
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
|
||||||
|
interface VatProps {
|
||||||
|
totalPrice: Price
|
||||||
|
vat: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const noVatCurrencies = [
|
||||||
|
CurrencyEnum.CC,
|
||||||
|
CurrencyEnum.POINTS,
|
||||||
|
CurrencyEnum.Voucher,
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function VatRow({ totalPrice, vat }: VatProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
if (noVatCurrencies.includes(totalPrice.local.currency)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const vatPercentage = vat / 100
|
||||||
|
const vatAmount = totalPrice.local.price * vatPercentage
|
||||||
|
|
||||||
|
const priceExclVat = totalPrice.local.price - vatAmount
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RegularRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "Price excluding VAT" })}
|
||||||
|
value={formatPrice(intl, priceExclVat, totalPrice.local.currency)}
|
||||||
|
/>
|
||||||
|
<RegularRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "VAT {vat}%" }, { vat })}
|
||||||
|
value={formatPrice(intl, vatAmount, totalPrice.local.currency)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { type TbodyProps,tbodyVariants } from "./variants"
|
||||||
|
|
||||||
|
export default function Tbody({ border, children }: TbodyProps) {
|
||||||
|
const classNames = tbodyVariants({ border })
|
||||||
|
return <tbody className={classNames}>{children}</tbody>
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.tbody {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody:has(tr > th) {
|
||||||
|
padding-top: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody:has(tr > th):not(:first-of-type),
|
||||||
|
.border {
|
||||||
|
border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody:not(:last-child) {
|
||||||
|
padding-bottom: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border {
|
||||||
|
padding-top: var(--Spacing-x2);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import styles from "./tbody.module.css"
|
||||||
|
|
||||||
|
import type { PropsWithChildren } from "react"
|
||||||
|
|
||||||
|
export const tbodyVariants = cva(styles.tbody, {
|
||||||
|
variants: {
|
||||||
|
border: {
|
||||||
|
true: styles.border,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface TbodyProps
|
||||||
|
extends PropsWithChildren,
|
||||||
|
VariantProps<typeof tbodyVariants> {}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
"use client"
|
||||||
|
import { Fragment } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
|
import BookingCodeRow from "./Row/BookingCode"
|
||||||
|
import DiscountedRegularPriceRow from "./Row/DiscountedRegularPrice"
|
||||||
|
import HeaderRow from "./Row/Header"
|
||||||
|
import LargeRow from "./Row/Large"
|
||||||
|
import CorporateChequePrice, {
|
||||||
|
type CorporateChequePriceType,
|
||||||
|
} from "./Row/Price/CorporateCheque"
|
||||||
|
import RedemptionPrice, {
|
||||||
|
type RedemptionPriceType,
|
||||||
|
} from "./Row/Price/Redemption"
|
||||||
|
import RegularPrice, { type RegularPriceType } from "./Row/Price/Regular"
|
||||||
|
import VoucherPrice, { type VoucherPriceType } from "./Row/Price/Voucher"
|
||||||
|
import VatRow from "./Row/Vat"
|
||||||
|
import Breakfast from "./Breakfast"
|
||||||
|
import Tbody from "./Tbody"
|
||||||
|
|
||||||
|
import styles from "./priceDetailsTable.module.css"
|
||||||
|
|
||||||
|
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
|
||||||
|
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||||
|
import type { Price } from "@/types/components/hotelReservation/price"
|
||||||
|
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
|
import type { Package, Packages } from "@/types/requests/packages"
|
||||||
|
|
||||||
|
type RoomPrice =
|
||||||
|
| CorporateChequePriceType
|
||||||
|
| RegularPriceType
|
||||||
|
| RedemptionPriceType
|
||||||
|
| VoucherPriceType
|
||||||
|
|
||||||
|
export interface Room {
|
||||||
|
adults: number
|
||||||
|
bedType: BedTypeSchema | undefined
|
||||||
|
breakfast: BreakfastPackage | false | undefined | null
|
||||||
|
breakfastIncluded: boolean
|
||||||
|
childrenInRoom: Child[] | undefined
|
||||||
|
packages: Packages | null
|
||||||
|
price: RoomPrice
|
||||||
|
roomType: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PriceDetailsTableProps {
|
||||||
|
bookingCode?: string
|
||||||
|
fromDate: string
|
||||||
|
rooms: Room[]
|
||||||
|
toDate: string
|
||||||
|
totalPrice: Price
|
||||||
|
vat: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PriceDetailsTable({
|
||||||
|
bookingCode,
|
||||||
|
fromDate,
|
||||||
|
rooms,
|
||||||
|
toDate,
|
||||||
|
totalPrice,
|
||||||
|
vat,
|
||||||
|
}: PriceDetailsTableProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const lang = useLang()
|
||||||
|
|
||||||
|
const diff = dt(toDate).diff(fromDate, "days")
|
||||||
|
const nights = intl.formatMessage(
|
||||||
|
{ defaultMessage: "{totalNights, plural, one {# night} other {# nights}}" },
|
||||||
|
{ totalNights: diff }
|
||||||
|
)
|
||||||
|
|
||||||
|
const arrival = dt(fromDate).locale(lang).format("ddd, D MMM")
|
||||||
|
const departue = dt(toDate).locale(lang).format("ddd, D MMM")
|
||||||
|
const duration = ` ${arrival} - ${departue} (${nights})`
|
||||||
|
|
||||||
|
const allRoomsPackages: Package[] = rooms
|
||||||
|
.flatMap((r) => r.packages)
|
||||||
|
.filter((r): r is Package => !!r)
|
||||||
|
return (
|
||||||
|
<table className={styles.priceDetailsTable}>
|
||||||
|
{rooms.map((room, idx) => {
|
||||||
|
let currency = ""
|
||||||
|
let chequePrice: CorporateChequePriceType["corporateCheque"] | undefined
|
||||||
|
if ("corporateCheque" in room.price && room.price.corporateCheque) {
|
||||||
|
chequePrice = room.price.corporateCheque
|
||||||
|
|
||||||
|
if (room.price.corporateCheque.currency) {
|
||||||
|
currency = room.price.corporateCheque.currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let price: RegularPriceType["regular"] | undefined
|
||||||
|
if ("regular" in room.price && room.price.regular) {
|
||||||
|
price = room.price.regular
|
||||||
|
currency = room.price.regular.currency
|
||||||
|
}
|
||||||
|
|
||||||
|
let redemptionPrice: RedemptionPriceType["redemption"] | undefined
|
||||||
|
if ("redemption" in room.price && room.price.redemption) {
|
||||||
|
redemptionPrice = room.price.redemption
|
||||||
|
|
||||||
|
if (room.price.redemption.currency) {
|
||||||
|
currency = room.price.redemption.currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let voucherPrice: VoucherPriceType["voucher"] | undefined
|
||||||
|
if ("voucher" in room.price && room.price.voucher) {
|
||||||
|
voucherPrice = room.price.voucher
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currency) {
|
||||||
|
if (room.packages?.length) {
|
||||||
|
currency = room.packages[0].localPrice.currency
|
||||||
|
} else if (room.breakfast) {
|
||||||
|
currency = room.breakfast.localPrice.currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!price && !voucherPrice && !chequePrice && !redemptionPrice) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment key={idx}>
|
||||||
|
<Tbody>
|
||||||
|
{rooms.length > 1 && (
|
||||||
|
<tr>
|
||||||
|
<th colSpan={2}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ defaultMessage: "Room {roomIndex}" },
|
||||||
|
{ roomIndex: idx + 1 }
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
<HeaderRow title={room.roomType} subtitle={duration} />
|
||||||
|
<RegularPrice
|
||||||
|
bedType={room.bedType}
|
||||||
|
packages={room.packages}
|
||||||
|
price={price}
|
||||||
|
/>
|
||||||
|
<CorporateChequePrice
|
||||||
|
bedType={room.bedType}
|
||||||
|
currency={currency}
|
||||||
|
nights={diff}
|
||||||
|
packages={room.packages}
|
||||||
|
price={chequePrice}
|
||||||
|
/>
|
||||||
|
<RedemptionPrice
|
||||||
|
bedType={room.bedType}
|
||||||
|
currency={currency}
|
||||||
|
nights={diff}
|
||||||
|
packages={room.packages}
|
||||||
|
price={redemptionPrice}
|
||||||
|
/>
|
||||||
|
<VoucherPrice
|
||||||
|
bedType={room.bedType}
|
||||||
|
currency={currency}
|
||||||
|
nights={diff}
|
||||||
|
packages={room.packages}
|
||||||
|
price={voucherPrice}
|
||||||
|
/>
|
||||||
|
</Tbody>
|
||||||
|
|
||||||
|
<Breakfast
|
||||||
|
adults={room.adults}
|
||||||
|
breakfast={room.breakfast}
|
||||||
|
breakfastIncluded={room.breakfastIncluded}
|
||||||
|
childrenInRoom={room.childrenInRoom}
|
||||||
|
currency={currency}
|
||||||
|
nights={diff}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<Tbody>
|
||||||
|
<HeaderRow title={intl.formatMessage({ defaultMessage: "Total" })} />
|
||||||
|
|
||||||
|
<VatRow totalPrice={totalPrice} vat={vat} />
|
||||||
|
|
||||||
|
<LargeRow
|
||||||
|
label={intl.formatMessage({ defaultMessage: "Price including VAT" })}
|
||||||
|
value={formatPrice(
|
||||||
|
intl,
|
||||||
|
totalPrice.local.price,
|
||||||
|
totalPrice.local.currency,
|
||||||
|
totalPrice.local.additionalPrice,
|
||||||
|
totalPrice.local.additionalPriceCurrency
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DiscountedRegularPriceRow
|
||||||
|
currency={totalPrice.local.currency}
|
||||||
|
packages={allRoomsPackages}
|
||||||
|
regularPrice={totalPrice.local.regularPrice}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BookingCodeRow bookingCode={bookingCode} />
|
||||||
|
</Tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.priceDetailsTable {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.priceDetailsTable {
|
||||||
|
min-width: 512px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Button } from "@scandic-hotels/design-system/Button"
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
|
||||||
|
import Modal from "@/components/Modal"
|
||||||
|
|
||||||
|
import PriceDetailsTable, {
|
||||||
|
type PriceDetailsTableProps,
|
||||||
|
} from "./PriceDetailsTable"
|
||||||
|
|
||||||
|
function Trigger({ title }: { title: string }) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="Text"
|
||||||
|
typography="Body/Supporting text (caption)/smBold"
|
||||||
|
wrapping={false}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
<MaterialIcon icon="chevron_right" color="CurrentColor" size={20} />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PriceDetailsModal(props: PriceDetailsTableProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const title = intl.formatMessage({ defaultMessage: "Price details" })
|
||||||
|
return (
|
||||||
|
<Modal title={title} trigger={<Trigger title={title} />}>
|
||||||
|
<PriceDetailsTable {...props} />
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
.priceDetailsTable {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price {
|
|
||||||
text-align: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:has(tr > th) {
|
|
||||||
padding-top: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:has(tr > th):not(:first-of-type) {
|
|
||||||
border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableSection:not(:last-child) {
|
|
||||||
padding-bottom: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.priceDetailsTable {
|
|
||||||
min-width: 512px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import { Button } from "@scandic-hotels/design-system/Button"
|
|||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
import { useRatesStore } from "@/stores/select-rate"
|
||||||
|
|
||||||
import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal"
|
import PriceDetailsModal from "@/components/HotelReservation/PriceDetailsModal"
|
||||||
import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop"
|
import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop"
|
||||||
@@ -18,7 +19,7 @@ import useLang from "@/hooks/useLang"
|
|||||||
import { formatPrice } from "@/utils/numberFormatting"
|
import { formatPrice } from "@/utils/numberFormatting"
|
||||||
|
|
||||||
import { isBookingCodeRate } from "./isBookingCodeRate"
|
import { isBookingCodeRate } from "./isBookingCodeRate"
|
||||||
import PriceDetailsTable from "./PriceDetailsTable"
|
import { mapToPrice } from "./mapToPrice"
|
||||||
|
|
||||||
import styles from "./summary.module.css"
|
import styles from "./summary.module.css"
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ export default function Summary({
|
|||||||
vat,
|
vat,
|
||||||
toggleSummaryOpen,
|
toggleSummaryOpen,
|
||||||
}: SelectRateSummaryProps) {
|
}: SelectRateSummaryProps) {
|
||||||
|
const rateSummary = useRatesStore((state) => state.rateSummary)
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
@@ -66,6 +68,8 @@ export default function Summary({
|
|||||||
)
|
)
|
||||||
const showDiscounted = containsBookingCodeRate || isMember
|
const showDiscounted = containsBookingCodeRate || isMember
|
||||||
|
|
||||||
|
const priceDetailsRooms = mapToPrice(rateSummary, booking, isMember)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.summary}>
|
<section className={styles.summary}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
@@ -304,17 +308,14 @@ export default function Summary({
|
|||||||
{ b: (str) => <b>{str}</b> }
|
{ b: (str) => <b>{str}</b> }
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<PriceDetailsModal>
|
<PriceDetailsModal
|
||||||
<PriceDetailsTable
|
bookingCode={booking.bookingCode}
|
||||||
bookingCode={booking.bookingCode}
|
fromDate={booking.fromDate}
|
||||||
fromDate={booking.fromDate}
|
rooms={priceDetailsRooms}
|
||||||
isMember={isMember}
|
toDate={booking.toDate}
|
||||||
rooms={rooms}
|
totalPrice={totalPrice}
|
||||||
toDate={booking.toDate}
|
vat={vat}
|
||||||
totalPrice={totalPrice}
|
/>
|
||||||
vat={vat}
|
|
||||||
/>
|
|
||||||
</PriceDetailsModal>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Body
|
<Body
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import type {
|
||||||
|
Rate,
|
||||||
|
SelectRateSearchParams,
|
||||||
|
} from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
|
import type { Room } from "@/components/HotelReservation/PriceDetailsModal/PriceDetailsTable"
|
||||||
|
|
||||||
|
export function mapToPrice(
|
||||||
|
rooms: (Rate | null)[],
|
||||||
|
booking: SelectRateSearchParams,
|
||||||
|
isUserLoggedIn: boolean
|
||||||
|
) {
|
||||||
|
return rooms
|
||||||
|
.map((room, idx) => {
|
||||||
|
if (!room) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let price = null
|
||||||
|
if ("corporateCheque" in room.product) {
|
||||||
|
price = {
|
||||||
|
corporateCheque: room.product.corporateCheque.localPrice,
|
||||||
|
}
|
||||||
|
} else if ("redemption" in room.product) {
|
||||||
|
price = {
|
||||||
|
redemption: room.product.redemption.localPrice,
|
||||||
|
}
|
||||||
|
} else if ("voucher" in room.product) {
|
||||||
|
price = {
|
||||||
|
voucher: room.product.voucher,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const isMainRoom = idx === 0
|
||||||
|
const memberRate = room.product.member
|
||||||
|
const onlyMemberRate = !room.product.public && memberRate
|
||||||
|
if ((isUserLoggedIn && isMainRoom && memberRate) || onlyMemberRate) {
|
||||||
|
price = {
|
||||||
|
regular: memberRate.localPrice,
|
||||||
|
}
|
||||||
|
} else if (room.product.public) {
|
||||||
|
price = {
|
||||||
|
regular: room.product.public.localPrice,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookingRoom = booking.rooms[idx]
|
||||||
|
return {
|
||||||
|
adults: bookingRoom.adults,
|
||||||
|
bedType: undefined,
|
||||||
|
breakfast: undefined,
|
||||||
|
breakfastIncluded: room.product.rateDefinition.breakfastIncluded,
|
||||||
|
childrenInRoom: bookingRoom.childrenInRoom,
|
||||||
|
packages: room.packages,
|
||||||
|
price,
|
||||||
|
roomType: room.roomType,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((r) => !!(r && r.price)) as Room[]
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { sumPackages } from "@/components/HotelReservation/utils"
|
||||||
|
|
||||||
import type { Price } from "@/types/components/hotelReservation/price"
|
import type { Price } from "@/types/components/hotelReservation/price"
|
||||||
import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import { CurrencyEnum } from "@/types/enums/currency"
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
import type { Packages } from "@/types/requests/packages"
|
||||||
import type { RedemptionProduct } from "@/types/trpc/routers/hotel/roomAvailability"
|
import type { RedemptionProduct } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||||
|
|
||||||
export function calculateTotalPrice(
|
export function calculateTotalPrice(
|
||||||
@@ -87,16 +90,28 @@ export function calculateTotalPrice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function calculateRedemptionTotalPrice(
|
export function calculateRedemptionTotalPrice(
|
||||||
redemption: RedemptionProduct["redemption"]
|
redemption: RedemptionProduct["redemption"],
|
||||||
|
packages: Packages | null
|
||||||
) {
|
) {
|
||||||
|
const pkgsSum = sumPackages(packages)
|
||||||
|
let additionalPrice
|
||||||
|
if (redemption.localPrice.additionalPricePerStay) {
|
||||||
|
additionalPrice =
|
||||||
|
redemption.localPrice.additionalPricePerStay + pkgsSum.price
|
||||||
|
} else if (pkgsSum.price) {
|
||||||
|
additionalPrice = pkgsSum.price
|
||||||
|
}
|
||||||
|
|
||||||
|
let additionalPriceCurrency
|
||||||
|
if (redemption.localPrice.currency) {
|
||||||
|
additionalPriceCurrency = redemption.localPrice.currency
|
||||||
|
} else if (pkgsSum.currency) {
|
||||||
|
additionalPriceCurrency = pkgsSum.currency
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
local: {
|
local: {
|
||||||
additionalPrice: redemption.localPrice.additionalPricePerStay
|
additionalPrice,
|
||||||
? redemption.localPrice.additionalPricePerStay
|
additionalPriceCurrency,
|
||||||
: undefined,
|
|
||||||
additionalPriceCurrency: redemption.localPrice.currency
|
|
||||||
? redemption.localPrice.currency
|
|
||||||
: undefined,
|
|
||||||
currency: CurrencyEnum.POINTS,
|
currency: CurrencyEnum.POINTS,
|
||||||
price: redemption.localPrice.pointsPerStay,
|
price: redemption.localPrice.pointsPerStay,
|
||||||
},
|
},
|
||||||
@@ -111,13 +126,9 @@ export function calculateVoucherPrice(selectedRateSummary: Rate[]) {
|
|||||||
}
|
}
|
||||||
const rate = room.product.voucher
|
const rate = room.product.voucher
|
||||||
|
|
||||||
return {
|
total.local.price = total.local.price + rate.numberOfVouchers
|
||||||
local: {
|
|
||||||
currency: total.local.currency,
|
return total
|
||||||
price: total.local.price + rate.numberOfVouchers,
|
|
||||||
},
|
|
||||||
requested: undefined,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
local: {
|
local: {
|
||||||
@@ -136,12 +147,17 @@ export function calculateCorporateChequePrice(selectedRateSummary: Rate[]) {
|
|||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
const rate = room.product.corporateCheque
|
const rate = room.product.corporateCheque
|
||||||
|
const pkgsSum = sumPackages(room.packages)
|
||||||
|
|
||||||
total.local.price = total.local.price + rate.localPrice.numberOfCheques
|
total.local.price = total.local.price + rate.localPrice.numberOfCheques
|
||||||
if (rate.localPrice.additionalPricePerStay) {
|
if (rate.localPrice.additionalPricePerStay) {
|
||||||
total.local.additionalPrice =
|
total.local.additionalPrice =
|
||||||
(total.local.additionalPrice || 0) +
|
(total.local.additionalPrice || 0) +
|
||||||
rate.localPrice.additionalPricePerStay
|
rate.localPrice.additionalPricePerStay +
|
||||||
|
pkgsSum.price
|
||||||
|
} else if (pkgsSum.price) {
|
||||||
|
total.local.additionalPrice =
|
||||||
|
(total.local.additionalPrice || 0) + pkgsSum.price
|
||||||
}
|
}
|
||||||
if (rate.localPrice.currency) {
|
if (rate.localPrice.currency) {
|
||||||
total.local.additionalPriceCurrency = rate.localPrice.currency
|
total.local.additionalPriceCurrency = rate.localPrice.currency
|
||||||
@@ -196,11 +212,11 @@ export function getTotalPrice(
|
|||||||
return calculateTotalPrice(summaryArray, isUserLoggedIn)
|
return calculateTotalPrice(summaryArray, isUserLoggedIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { product } = mainRoomProduct
|
const { packages, product } = mainRoomProduct
|
||||||
|
|
||||||
// In case of reward night (redemption) or voucher only single room booking is supported by business rules
|
// In case of reward night (redemption) or voucher only single room booking is supported by business rules
|
||||||
if ("redemption" in product) {
|
if ("redemption" in product) {
|
||||||
return calculateRedemptionTotalPrice(product.redemption)
|
return calculateRedemptionTotalPrice(product.redemption, packages)
|
||||||
}
|
}
|
||||||
if ("voucher" in product) {
|
if ("voucher" in product) {
|
||||||
return calculateVoucherPrice(summaryArray)
|
return calculateVoucherPrice(summaryArray)
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { useIntl } from "react-intl"
|
|||||||
import CampaignRateCard from "@scandic-hotels/design-system/CampaignRateCard"
|
import CampaignRateCard from "@scandic-hotels/design-system/CampaignRateCard"
|
||||||
import NoRateAvailableCard from "@scandic-hotels/design-system/NoRateAvailableCard"
|
import NoRateAvailableCard from "@scandic-hotels/design-system/NoRateAvailableCard"
|
||||||
|
|
||||||
|
import {
|
||||||
|
sumPackages,
|
||||||
|
sumPackagesRequestedPrice,
|
||||||
|
} from "@/components/HotelReservation/utils"
|
||||||
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||||
import useRateTitles from "@/hooks/booking/useRateTitles"
|
import useRateTitles from "@/hooks/booking/useRateTitles"
|
||||||
|
|
||||||
@@ -22,11 +26,15 @@ export default function Campaign({
|
|||||||
campaign,
|
campaign,
|
||||||
handleSelectRate,
|
handleSelectRate,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage,
|
|
||||||
roomTypeCode,
|
roomTypeCode,
|
||||||
}: CampaignProps) {
|
}: CampaignProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { roomNr, selectedFilter, selectedRate } = useRoomContext()
|
const {
|
||||||
|
roomNr,
|
||||||
|
selectedFilter,
|
||||||
|
selectedPackages,
|
||||||
|
selectedRate,
|
||||||
|
} = useRoomContext()
|
||||||
const rateTitles = useRateTitles()
|
const rateTitles = useRateTitles()
|
||||||
|
|
||||||
const isCampaignRate = campaign.some(
|
const isCampaignRate = campaign.some(
|
||||||
@@ -52,6 +60,9 @@ export default function Campaign({
|
|||||||
campaign = campaign.filter((product) => !product.bookingCode)
|
campaign = campaign.filter((product) => !product.bookingCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pkgsSum = sumPackages(selectedPackages)
|
||||||
|
const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages)
|
||||||
|
|
||||||
return campaign.map((product) => {
|
return campaign.map((product) => {
|
||||||
if (!product.public) {
|
if (!product.public) {
|
||||||
return (
|
return (
|
||||||
@@ -67,21 +78,21 @@ export default function Campaign({
|
|||||||
|
|
||||||
const rateTermDetails = product.rateDefinitionMember
|
const rateTermDetails = product.rateDefinitionMember
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: product.rateDefinition.title,
|
title: product.rateDefinition.title,
|
||||||
terms: product.rateDefinition.generalTerms,
|
terms: product.rateDefinition.generalTerms,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: product.rateDefinitionMember.title,
|
title: product.rateDefinitionMember.title,
|
||||||
terms: product.rateDefinition.generalTerms,
|
terms: product.rateDefinition.generalTerms,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
title: product.rateDefinition.title,
|
title: product.rateDefinition.title,
|
||||||
terms: product.rateDefinition.generalTerms,
|
terms: product.rateDefinition.generalTerms,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const isSelected = isSelectedPriceProduct(
|
const isSelected = isSelectedPriceProduct(
|
||||||
product,
|
product,
|
||||||
@@ -110,16 +121,18 @@ export default function Campaign({
|
|||||||
product.public.localPrice.pricePerNight,
|
product.public.localPrice.pricePerNight,
|
||||||
product.public.requestedPrice?.pricePerNight,
|
product.public.requestedPrice?.pricePerNight,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage
|
pkgsSum.price,
|
||||||
|
pkgsSumRequested.price
|
||||||
)
|
)
|
||||||
|
|
||||||
const pricePerNightMember = product.member
|
const pricePerNightMember = product.member
|
||||||
? calculatePricePerNightPriceProduct(
|
? calculatePricePerNightPriceProduct(
|
||||||
product.member.localPrice.pricePerNight,
|
product.member.localPrice.pricePerNight,
|
||||||
product.member.requestedPrice?.pricePerNight,
|
product.member.requestedPrice?.pricePerNight,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage
|
pkgsSum.price,
|
||||||
)
|
pkgsSumRequested.price
|
||||||
|
)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
let approximateRatePrice = undefined
|
let approximateRatePrice = undefined
|
||||||
@@ -135,12 +148,12 @@ export default function Campaign({
|
|||||||
const approximateRate =
|
const approximateRate =
|
||||||
approximateRatePrice && product.public.requestedPrice
|
approximateRatePrice && product.public.requestedPrice
|
||||||
? {
|
? {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Approx.",
|
defaultMessage: "Approx.",
|
||||||
}),
|
}),
|
||||||
price: approximateRatePrice,
|
price: approximateRatePrice,
|
||||||
unit: product.public.requestedPrice.currency,
|
unit: product.public.requestedPrice.currency,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -154,12 +167,12 @@ export default function Campaign({
|
|||||||
memberRate={
|
memberRate={
|
||||||
pricePerNightMember
|
pricePerNightMember
|
||||||
? {
|
? {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Member price",
|
defaultMessage: "Member price",
|
||||||
}),
|
}),
|
||||||
price: pricePerNightMember.totalPrice,
|
price: pricePerNightMember.totalPrice,
|
||||||
unit: `${product.member!.localPrice.currency}/${night}`,
|
unit: `${product.member!.localPrice.currency}/${night}`,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
name={`rateCode-${roomNr}-${product.public.rateCode}`}
|
name={`rateCode-${roomNr}-${product.public.rateCode}`}
|
||||||
@@ -173,15 +186,15 @@ export default function Campaign({
|
|||||||
omnibusRate={
|
omnibusRate={
|
||||||
product.public.localPrice.omnibusPricePerNight
|
product.public.localPrice.omnibusPricePerNight
|
||||||
? {
|
? {
|
||||||
label: intl
|
label: intl
|
||||||
.formatMessage({
|
.formatMessage({
|
||||||
defaultMessage: "Lowest price (last 30 days)",
|
defaultMessage: "Lowest price (last 30 days)",
|
||||||
})
|
})
|
||||||
.toUpperCase(),
|
.toUpperCase(),
|
||||||
price:
|
price:
|
||||||
product.public.localPrice.omnibusPricePerNight.toString(),
|
product.public.localPrice.omnibusPricePerNight.toString(),
|
||||||
unit: product.public.localPrice.currency,
|
unit: product.public.localPrice.currency,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
rateTermDetails={rateTermDetails}
|
rateTermDetails={rateTermDetails}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import CodeRateCard from "@scandic-hotels/design-system/CodeRateCard"
|
|||||||
|
|
||||||
import { useRatesStore } from "@/stores/select-rate"
|
import { useRatesStore } from "@/stores/select-rate"
|
||||||
|
|
||||||
|
import {
|
||||||
|
sumPackages,
|
||||||
|
sumPackagesRequestedPrice,
|
||||||
|
} from "@/components/HotelReservation/utils"
|
||||||
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||||
import useRateTitles from "@/hooks/booking/useRateTitles"
|
import useRateTitles from "@/hooks/booking/useRateTitles"
|
||||||
|
|
||||||
@@ -28,11 +32,11 @@ export default function Code({
|
|||||||
code,
|
code,
|
||||||
handleSelectRate,
|
handleSelectRate,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage,
|
|
||||||
roomTypeCode,
|
roomTypeCode,
|
||||||
}: CodeProps) {
|
}: CodeProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { roomNr, selectedFilter, selectedRate } = useRoomContext()
|
const { roomNr, selectedFilter, selectedPackages, selectedRate } =
|
||||||
|
useRoomContext()
|
||||||
const bookingCode = useRatesStore((state) => state.booking.bookingCode)
|
const bookingCode = useRatesStore((state) => state.booking.bookingCode)
|
||||||
const rateTitles = useRateTitles()
|
const rateTitles = useRateTitles()
|
||||||
const night = intl
|
const night = intl
|
||||||
@@ -74,11 +78,16 @@ export default function Code({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const pkgsSum = sumPackages(selectedPackages)
|
||||||
|
const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages)
|
||||||
|
|
||||||
if ("corporateCheque" in product) {
|
if ("corporateCheque" in product) {
|
||||||
const { localPrice, rateCode } = product.corporateCheque
|
const { localPrice, rateCode } = product.corporateCheque
|
||||||
let price = `${localPrice.numberOfCheques} CC`
|
let price = `${localPrice.numberOfCheques} CC`
|
||||||
if (localPrice.additionalPricePerStay) {
|
if (localPrice.additionalPricePerStay) {
|
||||||
price = `${price} + ${localPrice.additionalPricePerStay}`
|
price = `${price} + ${localPrice.additionalPricePerStay + pkgsSum.price}`
|
||||||
|
} else if (pkgsSum.price) {
|
||||||
|
price = `${price} + ${pkgsSum.price}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSelected = isSelectedCorporateCheque(
|
const isSelected = isSelectedCorporateCheque(
|
||||||
@@ -87,6 +96,8 @@ export default function Code({
|
|||||||
roomTypeCode
|
roomTypeCode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const currency = localPrice.currency ?? pkgsSum.currency?.toString() ?? ""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeRateCard
|
<CodeRateCard
|
||||||
key={product.rate}
|
key={product.rate}
|
||||||
@@ -98,7 +109,7 @@ export default function Code({
|
|||||||
rate={{
|
rate={{
|
||||||
label: product.rateDefinition?.title,
|
label: product.rateDefinition?.title,
|
||||||
price,
|
price,
|
||||||
unit: localPrice.currency ?? "",
|
unit: currency,
|
||||||
}}
|
}}
|
||||||
rateTitle={rateTitles[product.rate].title}
|
rateTitle={rateTitles[product.rate].title}
|
||||||
rateTermDetails={rateTermDetails}
|
rateTermDetails={rateTermDetails}
|
||||||
@@ -140,7 +151,8 @@ export default function Code({
|
|||||||
localPrice.pricePerNight,
|
localPrice.pricePerNight,
|
||||||
requestedPrice?.pricePerNight,
|
requestedPrice?.pricePerNight,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage
|
pkgsSum.price,
|
||||||
|
pkgsSumRequested.price
|
||||||
)
|
)
|
||||||
|
|
||||||
const approximateRate = pricePerNight.totalRequestedPrice
|
const approximateRate = pricePerNight.totalRequestedPrice
|
||||||
@@ -157,7 +169,8 @@ export default function Code({
|
|||||||
localPrice.regularPricePerNight,
|
localPrice.regularPricePerNight,
|
||||||
requestedPrice?.regularPricePerNight,
|
requestedPrice?.regularPricePerNight,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage
|
pkgsSum.price,
|
||||||
|
pkgsSumRequested.price
|
||||||
)
|
)
|
||||||
|
|
||||||
const comparisonRate =
|
const comparisonRate =
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import PointsRateCard from "@scandic-hotels/design-system/PointsRateCard"
|
import PointsRateCard from "@scandic-hotels/design-system/PointsRateCard"
|
||||||
|
|
||||||
|
import { sumPackages } from "@/components/HotelReservation/utils"
|
||||||
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||||
import useRateTitles from "@/hooks/booking/useRateTitles"
|
import useRateTitles from "@/hooks/booking/useRateTitles"
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ export default function Redemptions({
|
|||||||
}: RedemptionsProps) {
|
}: RedemptionsProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const rateTitles = useRateTitles()
|
const rateTitles = useRateTitles()
|
||||||
const { selectedFilter, selectedRate } = useRoomContext()
|
const { selectedFilter, selectedPackages, selectedRate } = useRoomContext()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
selectedFilter === BookingCodeFilterEnum.Discounted ||
|
selectedFilter === BookingCodeFilterEnum.Discounted ||
|
||||||
@@ -34,6 +35,8 @@ export default function Redemptions({
|
|||||||
const rewardNight = intl.formatMessage({
|
const rewardNight = intl.formatMessage({
|
||||||
defaultMessage: "Reward night",
|
defaultMessage: "Reward night",
|
||||||
})
|
})
|
||||||
|
const pkgsSum = sumPackages(selectedPackages)
|
||||||
|
|
||||||
const breakfastIncluded = intl.formatMessage({
|
const breakfastIncluded = intl.formatMessage({
|
||||||
defaultMessage: "Breakfast included",
|
defaultMessage: "Breakfast included",
|
||||||
})
|
})
|
||||||
@@ -58,20 +61,34 @@ export default function Redemptions({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rates = redemptions.map((r) => ({
|
const rates = redemptions.map((r) => {
|
||||||
additionalPrice:
|
let additionalPrice
|
||||||
r.redemption.localPrice.additionalPricePerStay &&
|
if (r.redemption.localPrice.additionalPricePerStay) {
|
||||||
r.redemption.localPrice.currency
|
additionalPrice =
|
||||||
? {
|
r.redemption.localPrice.additionalPricePerStay + pkgsSum.price
|
||||||
currency: r.redemption.localPrice.currency,
|
} else if (pkgsSum.price) {
|
||||||
price: r.redemption.localPrice.additionalPricePerStay.toString(),
|
additionalPrice = pkgsSum.price
|
||||||
|
}
|
||||||
|
let additionalPriceCurrency
|
||||||
|
if (r.redemption.localPrice.currency) {
|
||||||
|
additionalPriceCurrency = r.redemption.localPrice.currency
|
||||||
|
} else if (pkgsSum.currency) {
|
||||||
|
additionalPriceCurrency = pkgsSum.currency
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
additionalPrice:
|
||||||
|
additionalPrice && additionalPriceCurrency
|
||||||
|
? {
|
||||||
|
currency: additionalPriceCurrency,
|
||||||
|
price: additionalPrice.toString(),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
currency: "PTS",
|
currency: "PTS",
|
||||||
isDisabled: !r.redemption.hasEnoughPoints,
|
isDisabled: !r.redemption.hasEnoughPoints,
|
||||||
points: r.redemption.localPrice.pointsPerStay.toString(),
|
points: r.redemption.localPrice.pointsPerStay.toString(),
|
||||||
rateCode: r.redemption.rateCode,
|
rateCode: r.redemption.rateCode,
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const notEnoughPoints = rates.every((rate) => rate.isDisabled)
|
const notEnoughPoints = rates.every((rate) => rate.isDisabled)
|
||||||
const firstRedemption = redemptions[0]
|
const firstRedemption = redemptions[0]
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import RegularRateCard from "@scandic-hotels/design-system/RegularRateCard"
|
|||||||
|
|
||||||
import { useRatesStore } from "@/stores/select-rate"
|
import { useRatesStore } from "@/stores/select-rate"
|
||||||
|
|
||||||
|
import {
|
||||||
|
sumPackages,
|
||||||
|
sumPackagesRequestedPrice,
|
||||||
|
} from "@/components/HotelReservation/utils"
|
||||||
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||||
import useRateTitles from "@/hooks/booking/useRateTitles"
|
import useRateTitles from "@/hooks/booking/useRateTitles"
|
||||||
|
|
||||||
@@ -34,13 +38,13 @@ interface RegularProps extends SharedRateCardProps {
|
|||||||
export default function Regular({
|
export default function Regular({
|
||||||
handleSelectRate,
|
handleSelectRate,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage,
|
|
||||||
regular,
|
regular,
|
||||||
roomTypeCode,
|
roomTypeCode,
|
||||||
}: RegularProps) {
|
}: RegularProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const rateTitles = useRateTitles()
|
const rateTitles = useRateTitles()
|
||||||
const { isMainRoom, roomNr, selectedFilter, selectedRate } = useRoomContext()
|
const { isMainRoom, roomNr, selectedFilter, selectedPackages, selectedRate } =
|
||||||
|
useRoomContext()
|
||||||
const isUserLoggedIn = useRatesStore((state) => state.isUserLoggedIn)
|
const isUserLoggedIn = useRatesStore((state) => state.isUserLoggedIn)
|
||||||
|
|
||||||
if (selectedFilter === BookingCodeFilterEnum.Discounted) {
|
if (selectedFilter === BookingCodeFilterEnum.Discounted) {
|
||||||
@@ -52,6 +56,8 @@ export default function Regular({
|
|||||||
defaultMessage: "night",
|
defaultMessage: "night",
|
||||||
})
|
})
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
|
const pkgsSum = sumPackages(selectedPackages)
|
||||||
|
const pkgsSumRequested = sumPackagesRequestedPrice(selectedPackages)
|
||||||
|
|
||||||
return regular.map((product) => {
|
return regular.map((product) => {
|
||||||
const { member, public: standard } = product
|
const { member, public: standard } = product
|
||||||
@@ -81,19 +87,21 @@ export default function Regular({
|
|||||||
|
|
||||||
const memberPricePerNight = member
|
const memberPricePerNight = member
|
||||||
? calculatePricePerNightPriceProduct(
|
? calculatePricePerNightPriceProduct(
|
||||||
member.localPrice.pricePerNight,
|
member.localPrice.pricePerNight,
|
||||||
member.requestedPrice?.pricePerNight,
|
member.requestedPrice?.pricePerNight,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage
|
pkgsSum.price,
|
||||||
)
|
pkgsSumRequested.price
|
||||||
|
)
|
||||||
: undefined
|
: undefined
|
||||||
const standardPricePerNight = standard
|
const standardPricePerNight = standard
|
||||||
? calculatePricePerNightPriceProduct(
|
? calculatePricePerNightPriceProduct(
|
||||||
standard.localPrice.pricePerNight,
|
standard.localPrice.pricePerNight,
|
||||||
standard.requestedPrice?.pricePerNight,
|
standard.requestedPrice?.pricePerNight,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage
|
pkgsSum.price,
|
||||||
)
|
pkgsSumRequested.price
|
||||||
|
)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
let approximateMemberRatePrice = null
|
let approximateMemberRatePrice = null
|
||||||
@@ -141,12 +149,12 @@ export default function Regular({
|
|||||||
const approximateRate =
|
const approximateRate =
|
||||||
approximatePrice && requestedCurrency
|
approximatePrice && requestedCurrency
|
||||||
? {
|
? {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Approx.",
|
defaultMessage: "Approx.",
|
||||||
}),
|
}),
|
||||||
price: approximatePrice,
|
price: approximatePrice,
|
||||||
unit: requestedCurrency,
|
unit: requestedCurrency,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const isSelected = isSelectedPriceProduct(
|
const isSelected = isSelectedPriceProduct(
|
||||||
@@ -157,21 +165,21 @@ export default function Regular({
|
|||||||
|
|
||||||
const rateTermDetails = product.rateDefinitionMember
|
const rateTermDetails = product.rateDefinitionMember
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: product.rateDefinition.title,
|
title: product.rateDefinition.title,
|
||||||
terms: product.rateDefinition.generalTerms,
|
terms: product.rateDefinition.generalTerms,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: product.rateDefinitionMember.title,
|
title: product.rateDefinitionMember.title,
|
||||||
terms: product.rateDefinition.generalTerms,
|
terms: product.rateDefinition.generalTerms,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
title: product.rateDefinition.title,
|
title: product.rateDefinition.title,
|
||||||
terms: product.rateDefinition.generalTerms,
|
terms: product.rateDefinition.generalTerms,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RegularRateCard
|
<RegularRateCard
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import Redemptions from "./Redemptions"
|
|||||||
import Regular from "./Regular"
|
import Regular from "./Regular"
|
||||||
|
|
||||||
import type { RatesProps } from "@/types/components/hotelReservation/selectRate/rates"
|
import type { RatesProps } from "@/types/components/hotelReservation/selectRate/rates"
|
||||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
|
||||||
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
||||||
import type { Product } from "@/types/trpc/routers/hotel/roomAvailability"
|
import type { Product } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ export default function Rates({
|
|||||||
actions: { selectRate },
|
actions: { selectRate },
|
||||||
isFetchingAdditionalRate,
|
isFetchingAdditionalRate,
|
||||||
selectedFilter,
|
selectedFilter,
|
||||||
selectedPackages,
|
|
||||||
} = useRoomContext()
|
} = useRoomContext()
|
||||||
const nights = useRatesStore((state) =>
|
const nights = useRatesStore((state) =>
|
||||||
dt(state.booking.toDate).diff(state.booking.fromDate, "days")
|
dt(state.booking.toDate).diff(state.booking.fromDate, "days")
|
||||||
@@ -44,14 +42,9 @@ export default function Rates({
|
|||||||
selectRate({ features, product, roomType, roomTypeCode })
|
selectRate({ features, product, roomType, roomTypeCode })
|
||||||
}
|
}
|
||||||
|
|
||||||
const petRoomPackageSelected = selectedPackages.find(
|
|
||||||
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
|
||||||
)
|
|
||||||
|
|
||||||
const sharedProps = {
|
const sharedProps = {
|
||||||
handleSelectRate,
|
handleSelectRate,
|
||||||
nights,
|
nights,
|
||||||
petRoomPackage: petRoomPackageSelected,
|
|
||||||
roomTypeCode,
|
roomTypeCode,
|
||||||
}
|
}
|
||||||
const showAllRates = selectedFilter === BookingCodeFilterEnum.All
|
const showAllRates = selectedFilter === BookingCodeFilterEnum.All
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import type { Package } from "@/types/requests/packages"
|
|
||||||
|
|
||||||
export function calculatePricePerNightPriceProduct(
|
export function calculatePricePerNightPriceProduct(
|
||||||
pricePerNight: number,
|
pricePerNight: number,
|
||||||
requestedPricePerNight: number | undefined,
|
requestedPricePerNight: number | undefined,
|
||||||
nights: number,
|
nights: number,
|
||||||
petRoomPackage?: Package
|
packagesSumLocal: number,
|
||||||
|
packagesSumRequested: number
|
||||||
) {
|
) {
|
||||||
const totalPrice = petRoomPackage?.localPrice
|
const totalPrice = packagesSumLocal
|
||||||
? Math.floor(pricePerNight + petRoomPackage.localPrice.price / nights)
|
? Math.floor(pricePerNight + packagesSumLocal / nights)
|
||||||
: Math.floor(pricePerNight)
|
: Math.floor(pricePerNight)
|
||||||
|
|
||||||
let totalRequestedPrice = undefined
|
let totalRequestedPrice = undefined
|
||||||
if (requestedPricePerNight) {
|
if (requestedPricePerNight) {
|
||||||
if (petRoomPackage?.requestedPrice) {
|
if (packagesSumRequested) {
|
||||||
totalRequestedPrice = Math.floor(
|
totalRequestedPrice = Math.floor(
|
||||||
requestedPricePerNight + petRoomPackage.requestedPrice.price / nights
|
requestedPricePerNight + packagesSumRequested / nights
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
totalRequestedPrice = Math.floor(requestedPricePerNight)
|
totalRequestedPrice = Math.floor(requestedPricePerNight)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
type RoomPackageCodes,
|
type RoomPackageCodes,
|
||||||
} from "@/types/components/hotelReservation/selectRate/roomFilter"
|
} from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
|
import type { Packages } from "@/types/requests/packages"
|
||||||
|
|
||||||
interface IconForFeatureCodeProps {
|
interface IconForFeatureCodeProps {
|
||||||
featureCode: RoomPackageCodes
|
featureCode: RoomPackageCodes
|
||||||
@@ -53,3 +54,41 @@ export function generateChildrenString(children: Child[]): string {
|
|||||||
})
|
})
|
||||||
.join(",")}]`
|
.join(",")}]`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sumPackages(packages: Packages | null) {
|
||||||
|
if (!packages || !packages.length) {
|
||||||
|
return {
|
||||||
|
currency: undefined,
|
||||||
|
price: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return packages.reduce(
|
||||||
|
(total, pkg) => {
|
||||||
|
total.price = total.price + pkg.localPrice.totalPrice
|
||||||
|
return total
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency: packages[0].localPrice.currency,
|
||||||
|
price: 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sumPackagesRequestedPrice(packages: Packages | null) {
|
||||||
|
if (!packages || !packages.length) {
|
||||||
|
return {
|
||||||
|
currency: undefined,
|
||||||
|
price: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return packages.reduce(
|
||||||
|
(total, pkg) => {
|
||||||
|
total.price = total.price + pkg.requestedPrice.totalPrice
|
||||||
|
return total
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency: packages[0].requestedPrice.currency,
|
||||||
|
price: 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import isEqual from "fast-deep-equal"
|
import isEqual from "fast-deep-equal"
|
||||||
|
|
||||||
|
import {
|
||||||
|
sumPackages,
|
||||||
|
sumPackagesRequestedPrice,
|
||||||
|
} from "@/components/HotelReservation/utils"
|
||||||
|
|
||||||
import { detailsStorageName } from "."
|
import { detailsStorageName } from "."
|
||||||
|
|
||||||
import { type RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
import { type RoomRate } from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
@@ -423,6 +428,11 @@ export function calcTotalPrice(
|
|||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSpecialRate =
|
||||||
|
"corporateCheque" in room.roomRate ||
|
||||||
|
"redemption" in room.roomRate ||
|
||||||
|
"voucher" in room.roomRate
|
||||||
|
|
||||||
const breakfastRequestedPrice = room.breakfast
|
const breakfastRequestedPrice = room.breakfast
|
||||||
? (room.breakfast.requestedPrice?.price ?? 0)
|
? (room.breakfast.requestedPrice?.price ?? 0)
|
||||||
: 0
|
: 0
|
||||||
@@ -430,21 +440,11 @@ export function calcTotalPrice(
|
|||||||
? (room.breakfast.localPrice?.price ?? 0)
|
? (room.breakfast.localPrice?.price ?? 0)
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
const roomFeaturesTotal = (room.roomFeatures || []).reduce(
|
const pkgsSum = sumPackages(room.roomFeatures)
|
||||||
(total, pkg) => {
|
const pkgsSumRequested = sumPackagesRequestedPrice(room.roomFeatures)
|
||||||
if (pkg.requestedPrice.totalPrice) {
|
|
||||||
total.requestedPrice = add(
|
|
||||||
total.requestedPrice,
|
|
||||||
pkg.requestedPrice.totalPrice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
total.local = add(total.local, pkg.localPrice.totalPrice)
|
|
||||||
|
|
||||||
return total
|
|
||||||
},
|
|
||||||
{ local: 0, requestedPrice: 0 }
|
|
||||||
)
|
|
||||||
|
|
||||||
|
const breakfastRequestedTotalPrice =
|
||||||
|
breakfastRequestedPrice * room.adults * nights
|
||||||
if (roomPrice.perStay.requested) {
|
if (roomPrice.perStay.requested) {
|
||||||
if (!acc.requested) {
|
if (!acc.requested) {
|
||||||
acc.requested = {
|
acc.requested = {
|
||||||
@@ -453,61 +453,84 @@ export function calcTotalPrice(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acc.requested.price = add(
|
if (isSpecialRate) {
|
||||||
acc.requested.price,
|
acc.requested.price = add(
|
||||||
roomPrice.perStay.requested.price,
|
acc.requested.price,
|
||||||
breakfastRequestedPrice * room.adults * nights
|
roomPrice.perStay.requested.price
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Come back and verify on CC, PTS, Voucher
|
|
||||||
if (roomPrice.perStay.requested.additionalPrice) {
|
|
||||||
acc.requested.additionalPrice = add(
|
|
||||||
acc.requested.additionalPrice,
|
|
||||||
roomPrice.perStay.requested.additionalPrice
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
acc.requested.additionalPrice = add(
|
||||||
roomPrice.perStay.requested.additionalPriceCurrency &&
|
breakfastRequestedTotalPrice,
|
||||||
!acc.requested.additionalPriceCurrency
|
pkgsSumRequested.price
|
||||||
) {
|
)
|
||||||
acc.requested.additionalPriceCurrency =
|
|
||||||
roomPrice.perStay.requested.additionalPriceCurrency
|
if (!acc.requested.additionalPriceCurrency) {
|
||||||
|
if (roomPrice.perStay.requested.additionalPriceCurrency) {
|
||||||
|
acc.requested.additionalPriceCurrency =
|
||||||
|
roomPrice.perStay.requested.additionalPriceCurrency
|
||||||
|
} else if (room.breakfast) {
|
||||||
|
acc.requested.additionalPriceCurrency =
|
||||||
|
room.breakfast.localPrice.currency
|
||||||
|
} else if (pkgsSumRequested.currency) {
|
||||||
|
acc.requested.additionalPriceCurrency = pkgsSumRequested.currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc.requested.price = add(
|
||||||
|
acc.requested.price,
|
||||||
|
roomPrice.perStay.requested.price,
|
||||||
|
breakfastRequestedTotalPrice,
|
||||||
|
pkgsSumRequested.price
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const breakfastLocalTotalPrice =
|
const breakfastLocalTotalPrice =
|
||||||
breakfastLocalPrice * room.adults * nights
|
breakfastLocalPrice * room.adults * nights
|
||||||
|
|
||||||
acc.local.price = add(
|
if (isSpecialRate) {
|
||||||
acc.local.price,
|
acc.local.price = add(acc.local.price, roomPrice.perStay.local.price)
|
||||||
roomPrice.perStay.local.price,
|
|
||||||
breakfastLocalTotalPrice,
|
|
||||||
roomFeaturesTotal.local
|
|
||||||
)
|
|
||||||
|
|
||||||
if (roomPrice.perStay.local.regularPrice) {
|
if (
|
||||||
acc.local.regularPrice = add(
|
roomPrice.perStay.local.additionalPrice ||
|
||||||
acc.local.regularPrice,
|
breakfastLocalTotalPrice ||
|
||||||
roomPrice.perStay.local.regularPrice,
|
pkgsSum.price
|
||||||
|
) {
|
||||||
|
acc.local.additionalPrice = add(
|
||||||
|
acc.local.additionalPrice,
|
||||||
|
roomPrice.perStay.local.additionalPrice,
|
||||||
|
breakfastLocalTotalPrice,
|
||||||
|
pkgsSum.price
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acc.local.additionalPriceCurrency) {
|
||||||
|
if (roomPrice.perStay.local.additionalPriceCurrency) {
|
||||||
|
acc.local.additionalPriceCurrency =
|
||||||
|
roomPrice.perStay.local.additionalPriceCurrency
|
||||||
|
} else if (room.breakfast) {
|
||||||
|
acc.local.additionalPriceCurrency =
|
||||||
|
room.breakfast.localPrice.currency
|
||||||
|
} else if (pkgsSum.currency) {
|
||||||
|
acc.local.additionalPriceCurrency = pkgsSum.currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc.local.price = add(
|
||||||
|
acc.local.price,
|
||||||
|
roomPrice.perStay.local.price,
|
||||||
breakfastLocalTotalPrice,
|
breakfastLocalTotalPrice,
|
||||||
roomFeaturesTotal.local
|
pkgsSum.price
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if (roomPrice.perStay.local.additionalPrice) {
|
if (roomPrice.perStay.local.regularPrice) {
|
||||||
acc.local.additionalPrice = add(
|
acc.local.regularPrice = add(
|
||||||
acc.local.additionalPrice,
|
acc.local.regularPrice,
|
||||||
roomPrice.perStay.local.additionalPrice
|
roomPrice.perStay.local.regularPrice,
|
||||||
)
|
breakfastLocalTotalPrice,
|
||||||
}
|
pkgsSum.price
|
||||||
|
)
|
||||||
if (
|
}
|
||||||
roomPrice.perStay.local.additionalPriceCurrency &&
|
|
||||||
!acc.local.additionalPriceCurrency
|
|
||||||
) {
|
|
||||||
acc.local.additionalPriceCurrency =
|
|
||||||
roomPrice.perStay.local.additionalPriceCurrency
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import { create, useStore } from "zustand"
|
|||||||
import { REDEMPTION } from "@/constants/booking"
|
import { REDEMPTION } from "@/constants/booking"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
|
import {
|
||||||
|
sumPackages,
|
||||||
|
sumPackagesRequestedPrice,
|
||||||
|
} from "@/components/HotelReservation/utils"
|
||||||
import { DetailsContext } from "@/contexts/Details"
|
import { DetailsContext } from "@/contexts/Details"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -64,6 +68,7 @@ export function createDetailsStore(
|
|||||||
|
|
||||||
let initialTotalPrice: Price
|
let initialTotalPrice: Price
|
||||||
const roomOneRoomRate = initialState.rooms[0].roomRate
|
const roomOneRoomRate = initialState.rooms[0].roomRate
|
||||||
|
const initialRoomRates = initialState.rooms.map((r) => r.roomRate)
|
||||||
if (isRedemption && "redemption" in roomOneRoomRate) {
|
if (isRedemption && "redemption" in roomOneRoomRate) {
|
||||||
initialTotalPrice = {
|
initialTotalPrice = {
|
||||||
local: {
|
local: {
|
||||||
@@ -80,34 +85,56 @@ export function createDetailsStore(
|
|||||||
roomOneRoomRate.redemption.localPrice.additionalPricePerStay
|
roomOneRoomRate.redemption.localPrice.additionalPricePerStay
|
||||||
}
|
}
|
||||||
} else if (isVoucher) {
|
} else if (isVoucher) {
|
||||||
initialTotalPrice = calculateVoucherPrice(
|
initialTotalPrice = calculateVoucherPrice(initialRoomRates)
|
||||||
initialState.rooms.map((r) => r.roomRate)
|
|
||||||
)
|
|
||||||
} else if (isCorpChq) {
|
} else if (isCorpChq) {
|
||||||
initialTotalPrice = calculateCorporateChequePrice(
|
initialTotalPrice = calculateCorporateChequePrice(initialRoomRates)
|
||||||
initialState.rooms.map((r) => r.roomRate)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
initialTotalPrice = getTotalPrice(
|
initialTotalPrice = getTotalPrice(initialRoomRates, isMember)
|
||||||
initialState.rooms.map((r) => r.roomRate),
|
|
||||||
isMember
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialState.rooms.forEach((room) => {
|
initialState.rooms.forEach((room) => {
|
||||||
if (room.roomFeatures) {
|
if (room.roomFeatures) {
|
||||||
room.roomFeatures.forEach((pkg) => {
|
const pkgsSum = sumPackages(room.roomFeatures)
|
||||||
|
const pkgsSumRequested = sumPackagesRequestedPrice(room.roomFeatures)
|
||||||
|
|
||||||
|
if ("corporateCheque" in room.roomRate || "redemption" in room.roomRate) {
|
||||||
|
initialTotalPrice.local.additionalPrice = add(
|
||||||
|
initialTotalPrice.local.additionalPrice,
|
||||||
|
pkgsSum.price
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
!initialTotalPrice.local.additionalPriceCurrency &&
|
||||||
|
pkgsSum.currency
|
||||||
|
) {
|
||||||
|
initialTotalPrice.local.additionalPriceCurrency = pkgsSum.currency
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialTotalPrice.requested) {
|
||||||
|
initialTotalPrice.requested.additionalPrice = add(
|
||||||
|
initialTotalPrice.requested.additionalPrice,
|
||||||
|
pkgsSumRequested.price
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
!initialTotalPrice.requested.additionalPriceCurrency &&
|
||||||
|
pkgsSumRequested.currency
|
||||||
|
) {
|
||||||
|
initialTotalPrice.requested.additionalPriceCurrency =
|
||||||
|
pkgsSumRequested.currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("public" in room.roomRate) {
|
||||||
if (initialTotalPrice.requested) {
|
if (initialTotalPrice.requested) {
|
||||||
initialTotalPrice.requested.price = add(
|
initialTotalPrice.requested.price = add(
|
||||||
initialTotalPrice.requested.price,
|
initialTotalPrice.requested.price,
|
||||||
pkg.requestedPrice.totalPrice
|
pkgsSumRequested.price
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
initialTotalPrice.local.price = add(
|
initialTotalPrice.local.price = add(
|
||||||
initialTotalPrice.local.price,
|
initialTotalPrice.local.price,
|
||||||
pkg.localPrice.totalPrice
|
pkgsSum.price
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ interface RoomPrice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface MyStayTotalPriceState {
|
interface MyStayTotalPriceState {
|
||||||
rooms: RoomPrice[]
|
|
||||||
totalPrice: number | null
|
|
||||||
currencyCode: CurrencyEnum
|
currencyCode: CurrencyEnum
|
||||||
|
rooms: RoomPrice[]
|
||||||
|
totalCheques: number
|
||||||
totalPoints: number
|
totalPoints: number
|
||||||
|
totalPrice: number | null
|
||||||
|
totalVouchers: number
|
||||||
actions: {
|
actions: {
|
||||||
// Add a single room price
|
// Add a single room price
|
||||||
addRoomPrice: (room: RoomPrice) => void
|
addRoomPrice: (room: RoomPrice) => void
|
||||||
|
|||||||
@@ -89,12 +89,14 @@ export function createRatesStore({
|
|||||||
room.counterRateCode
|
room.counterRateCode
|
||||||
)
|
)
|
||||||
if (product) {
|
if (product) {
|
||||||
|
const roomPackages = roomsPackages[idx].filter((pkg) =>
|
||||||
|
room.packages?.includes(pkg.code)
|
||||||
|
)
|
||||||
|
|
||||||
rateSummary[idx] = {
|
rateSummary[idx] = {
|
||||||
features: selectedRoom.features,
|
features: selectedRoom.features,
|
||||||
product,
|
product,
|
||||||
packages: roomsPackages[idx].filter((pkg) =>
|
packages: roomPackages,
|
||||||
room.packages?.includes(pkg.code)
|
|
||||||
),
|
|
||||||
rate: product.rate,
|
rate: product.rate,
|
||||||
roomType: selectedRoom.roomType,
|
roomType: selectedRoom.roomType,
|
||||||
roomTypeCode: selectedRoom.roomTypeCode,
|
roomTypeCode: selectedRoom.roomTypeCode,
|
||||||
|
|||||||
@@ -3,4 +3,11 @@ export enum MODAL_STEPS {
|
|||||||
CONFIRMATION = 2,
|
CONFIRMATION = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PriceType = "points" | "money" | "voucher" | "cheque"
|
export enum PriceTypeEnum {
|
||||||
|
cheque = "cheque",
|
||||||
|
money = "money",
|
||||||
|
points = "points",
|
||||||
|
voucher = "voucher",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PriceType = keyof typeof PriceTypeEnum
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { z } from "zod"
|
|||||||
import { CurrencyEnum } from "@/types/enums/currency"
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
|
||||||
interface TPrice {
|
interface TPrice {
|
||||||
|
additionalPrice?: number
|
||||||
|
additionalPriceCurrency?: CurrencyEnum
|
||||||
currency: CurrencyEnum
|
currency: CurrencyEnum
|
||||||
price: number
|
price: number
|
||||||
regularPrice?: number
|
regularPrice?: number
|
||||||
additionalPrice?: number
|
|
||||||
additionalPriceCurrency?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Price {
|
export interface Price {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { Package } from "@/types/requests/packages"
|
|
||||||
import type {
|
import type {
|
||||||
Product,
|
Product,
|
||||||
RoomConfiguration,
|
RoomConfiguration,
|
||||||
@@ -12,5 +11,4 @@ export interface SharedRateCardProps
|
|||||||
extends Pick<RoomConfiguration, "roomTypeCode"> {
|
extends Pick<RoomConfiguration, "roomTypeCode"> {
|
||||||
handleSelectRate: (product: Product) => void
|
handleSelectRate: (product: Product) => void
|
||||||
nights: number
|
nights: number
|
||||||
petRoomPackage: Package | undefined
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import type { CurrencyEnum } from "../enums/currency"
|
||||||
import type { Room } from "../stores/booking-confirmation"
|
import type { Room } from "../stores/booking-confirmation"
|
||||||
|
|
||||||
export interface BookingConfirmationProviderProps
|
export interface BookingConfirmationProviderProps
|
||||||
extends React.PropsWithChildren {
|
extends React.PropsWithChildren {
|
||||||
bookingCode: string | null
|
bookingCode: string | null
|
||||||
currencyCode: string
|
currencyCode: CurrencyEnum
|
||||||
fromDate: Date
|
fromDate: Date
|
||||||
rooms: (Room | null)[]
|
rooms: (Room | null)[]
|
||||||
toDate: Date
|
toDate: Date
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { ChildBedTypeEnum } from "@/constants/booking"
|
import type { ChildBedTypeEnum } from "@/constants/booking"
|
||||||
|
import type { CurrencyEnum } from "../enums/currency"
|
||||||
import type {
|
import type {
|
||||||
BookingConfirmation,
|
BookingConfirmation,
|
||||||
PackageSchema,
|
PackageSchema,
|
||||||
@@ -19,7 +20,7 @@ export interface Room {
|
|||||||
childBedPreferences: ChildBedPreference[]
|
childBedPreferences: ChildBedPreference[]
|
||||||
childrenAges?: number[]
|
childrenAges?: number[]
|
||||||
confirmationNumber: string
|
confirmationNumber: string
|
||||||
currencyCode: string
|
currencyCode: CurrencyEnum
|
||||||
fromDate: Date
|
fromDate: Date
|
||||||
name: string
|
name: string
|
||||||
packages: BookingConfirmation["booking"]["packages"]
|
packages: BookingConfirmation["booking"]["packages"]
|
||||||
@@ -41,7 +42,7 @@ export interface InitialState {
|
|||||||
fromDate: Date
|
fromDate: Date
|
||||||
rooms: (Room | null)[]
|
rooms: (Room | null)[]
|
||||||
toDate: Date
|
toDate: Date
|
||||||
currencyCode: string
|
currencyCode: CurrencyEnum
|
||||||
vat: number
|
vat: number
|
||||||
isVatCurrency: boolean
|
isVatCurrency: boolean
|
||||||
formattedTotalCost: string
|
formattedTotalCost: string
|
||||||
|
|||||||
Reference in New Issue
Block a user