fix: always use totalPrice to display roomCharge

This commit is contained in:
Simon Emanuelsson
2025-04-16 12:41:37 +02:00
parent 1f94c581ae
commit 722d4505ba
18 changed files with 312 additions and 864 deletions

View File

@@ -0,0 +1,40 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useMyStayTotalPriceStore } from "@/stores/my-stay/myStayTotalPrice"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import { formatPrice } from "@/utils/numberFormatting"
import { CurrencyEnum } from "@/types/enums/currency"
export default function Cheques({
cheques,
price,
}: {
cheques: number
price: number
}) {
const intl = useIntl()
const currencyCode = useMyStayTotalPriceStore((state) => state.currencyCode)
if (!cheques) {
return <SkeletonShimmer width={"100px"} />
}
const totalPrice = formatPrice(
intl,
cheques,
CurrencyEnum.CC,
price,
currencyCode
)
return (
<Typography variant="Title/Subtitle/lg">
<p>{totalPrice}</p>
</Typography>
)
}

View File

@@ -16,8 +16,7 @@ import IconChip from "@/components/TempDesignSystem/IconChip"
import useLang from "@/hooks/useLang"
import { IconForFeatureCode } from "../../utils"
import Points from "../Points"
import Price from "../Price"
import PriceType from "../PriceType"
import { hasModifiableRate } from "../utils"
import { hasBreakfastPackageFromBookingFlow } from "../utils/hasBreakfastPackage"
import { mapRoomDetails } from "../utils/mapRoomDetails"
@@ -119,17 +118,18 @@ export default function MultiRoom({
const {
adults,
checkInDate,
cheques,
childrenAges,
confirmationNumber,
cancellationNumber,
hotelId,
roomPoints,
roomPrice,
packages,
rateDefinition,
isCancelled,
priceType,
vouchers,
totalPrice,
} = multiRoom
const fromDate = dt(checkInDate).locale(lang)
@@ -349,26 +349,15 @@ export default function MultiRoom({
})}
</p>
</Typography>
{priceType === "points" ? (
<Points points={roomPoints} variant="Body/Paragraph/mdBold" />
) : priceType === "voucher" ? (
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage(
{
defaultMessage: "{count} voucher",
},
{ count: vouchers }
)}
</p>
</Typography>
) : (
<Price
price={isCancelled ? 0 : roomPrice.perStay.local.price}
variant="Body/Paragraph/mdBold"
isMember={rateDefinition.isMemberRate}
/>
)}
<PriceType
cheques={cheques}
isCancelled={isCancelled}
priceType={priceType}
rateDefinition={rateDefinition}
roomPoints={roomPoints}
totalPrice={totalPrice}
vouchers={vouchers}
/>
</div>
</div>
</div>

View File

@@ -1,330 +0,0 @@
"use client"
import { Fragment } from "react"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { dt } from "@/lib/dt"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import useLang from "@/hooks/useLang"
import { formatPrice } from "@/utils/numberFormatting"
import styles from "./priceDetailsTable.module.css"
import type { Price } from "@/types/components/hotelReservation/price"
import type { Room } from "@/stores/my-stay/myStayRoomDetailsStore"
function Row({
label,
value,
bold,
}: {
label: string
value: string
bold?: boolean
}) {
return (
<tr className={styles.row}>
<td>
<Typography
variant={
bold
? "Body/Supporting text (caption)/smBold"
: "Body/Supporting text (caption)/smRegular"
}
>
<span>{label}</span>
</Typography>
</td>
<td className={styles.price}>
<Typography
variant={
bold
? "Body/Supporting text (caption)/smBold"
: "Body/Supporting text (caption)/smRegular"
}
>
<span>{value}</span>
</Typography>
</td>
</tr>
)
}
function TableSection({ children }: React.PropsWithChildren) {
return <tbody className={styles.tableSection}>{children}</tbody>
}
function TableSectionHeader({
title,
subtitle,
}: {
title: string
subtitle?: string
}) {
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>
)}
</>
)
}
export type RoomPriceDetails = Pick<
Room,
| "adults"
| "bedType"
| "breakfast"
| "childrenInRoom"
| "roomPrice"
| "roomName"
| "packages"
| "isCancelled"
> & {
guest?: Room["guest"]
}
export interface PriceDetailsTableProps {
bookingCode?: string | null
fromDate: string
bookedRoom: RoomPriceDetails
linkedReservationRooms: RoomPriceDetails[]
toDate: string
totalPrice: Price
vat: number
}
export default function PriceDetailsTable({
bookingCode,
fromDate,
bookedRoom,
linkedReservationRooms,
toDate,
totalPrice,
vat,
}: PriceDetailsTableProps) {
const intl = useIntl()
const lang = useLang()
const rooms = [bookedRoom, ...linkedReservationRooms].filter(
(room) => !room.isCancelled
)
const diff = dt(toDate).diff(fromDate, "days")
const nights = intl.formatMessage(
{
defaultMessage: "{totalNights, plural, one {# night} other {# nights}}",
},
{ totalNights: diff }
)
const vatPercentage = vat / 100
const vatAmount = totalPrice.local.price * vatPercentage
const priceExclVat = totalPrice.local.price - vatAmount
const duration = ` ${dt(fromDate).locale(lang).format("ddd, D MMM")}
-
${dt(toDate).locale(lang).format("ddd, D MMM")} (${nights})`
return (
<table className={styles.priceDetailsTable}>
{rooms.map((room, idx) => {
const totalAdultsMsg = intl.formatMessage(
{
defaultMessage:
"{totalAdults, plural, one {# adult} other {# adults}}",
},
{
totalAdults: room.adults,
}
)
const totalChildrenMsg = intl.formatMessage(
{
defaultMessage:
"{totalChildren, plural, one {# child} other {# children}}",
},
{
totalChildren: room.childrenInRoom.length,
}
)
const guestsCountMsg = [totalAdultsMsg]
if (room.childrenInRoom.length) {
guestsCountMsg.push(totalChildrenMsg)
}
return (
<Fragment key={idx}>
<TableSection>
{rooms.length > 1 && (
<tr>
<td>
<Typography variant="Body/Paragraph/mdBold">
<span>
{intl.formatMessage(
{
defaultMessage: "Room {roomIndex}",
},
{ roomIndex: idx + 1 }
)}
</span>
</Typography>
</td>
</tr>
)}
<TableSectionHeader title={room.roomName} subtitle={duration} />
<Row
label={intl.formatMessage({
defaultMessage: "Average price per night",
})}
value={formatPrice(
intl,
room.roomPrice.perNight.local.price,
room.roomPrice.perNight.local.currency
)}
/>
{room.packages
? room.packages.map((feature) => (
<Row
key={feature.code}
label={feature.description}
value={formatPrice(
intl,
+feature.localPrice.totalPrice,
feature.localPrice.currency
)}
/>
))
: null}
<Row
bold
label={intl.formatMessage({
defaultMessage: "Room charge",
})}
value={formatPrice(
intl,
room.roomPrice.perStay.local.price,
room.roomPrice.perStay.local.currency
)}
/>
</TableSection>
{room.breakfast ? (
<TableSection>
<Row
label={intl.formatMessage(
{
defaultMessage:
"Breakfast ({guestsCount}) x {totalBreakfasts}",
},
{
guestsCount: guestsCountMsg.join(", "),
totalBreakfasts: diff,
}
)}
value={formatPrice(
intl,
room.breakfast.localPrice.totalPrice,
room.breakfast.localPrice.currency
)}
/>
<Row
bold
label={intl.formatMessage({
defaultMessage: "Breakfast charge",
})}
value={formatPrice(
intl,
room.breakfast.localPrice.totalPrice,
room.breakfast.localPrice.currency
)}
/>
</TableSection>
) : null}
</Fragment>
)
})}
<TableSection>
<TableSectionHeader
title={intl.formatMessage({
defaultMessage: "Total",
})}
/>
<Row
label={intl.formatMessage({
defaultMessage: "Price excluding VAT",
})}
value={formatPrice(intl, priceExclVat, totalPrice.local.currency)}
/>
<Row
label={intl.formatMessage(
{
defaultMessage: "VAT {vat}%",
},
{ vat }
)}
value={formatPrice(intl, vatAmount, totalPrice.local.currency)}
/>
<tr className={styles.row}>
<td>
<Body textTransform="bold">
{intl.formatMessage({
defaultMessage: "Price including VAT",
})}
</Body>
</td>
<td className={styles.price}>
<Body textTransform="bold">
{formatPrice(
intl,
totalPrice.local.price,
totalPrice.local.currency
)}
</Body>
</td>
</tr>
{totalPrice.local.regularPrice && (
<tr className={styles.row}>
<td></td>
<td className={styles.price}>
<Caption color="uiTextMediumContrast" striked={true}>
{formatPrice(
intl,
totalPrice.local.regularPrice,
totalPrice.local.currency
)}
</Caption>
</td>
</tr>
)}
{bookingCode && totalPrice.local.regularPrice && (
<tr className={styles.row}>
<td>
<MaterialIcon icon="sell" />
{bookingCode}
</td>
<td></td>
</tr>
)}
</TableSection>
</table>
)
}

View File

@@ -1,7 +1,5 @@
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"
@@ -12,7 +10,7 @@ export function mapToPrice(room: Room) {
case PriceTypeEnum.cheque:
return {
corporateCheque: {
additionalPricePerStay: room.roomPrice.perStay.local.price,
additionalPricePerStay: room.totalPrice,
currency: room.roomPrice.perStay.local.currency,
numberOfCheques: room.cheques,
},
@@ -21,8 +19,8 @@ export function mapToPrice(room: Room) {
return {
regular: {
currency: room.currencyCode,
pricePerNight: room.roomPrice.perNight,
pricePerStay: room.roomPrice.perStay,
pricePerNight: room.roomPrice.perNight.local.price,
pricePerStay: room.totalPrice,
},
}
case PriceTypeEnum.points:
@@ -31,8 +29,8 @@ export function mapToPrice(room: Room) {
.diff(dt(room.checkInDate).startOf("day"), "days")
return {
redemption: {
additionalPricePerStay: room.roomPrice.perStay.local.price,
currency: room.roomPrice.perStay.local.currency,
additionalPricePerStay: room.totalPrice,
currency: room.currencyCode,
pointsPerNight: room.roomPoints / nights,
pointsPerStay: room.roomPoints,
},
@@ -51,11 +49,6 @@ export function mapToPrice(room: Room) {
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:
{
@@ -65,11 +58,7 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
break
case PriceTypeEnum.money:
{
total.local.price =
total.local.price +
room.roomPrice.perStay.local.price +
pkgsSum.price +
breakfastPrice
total.local.price = total.local.price + room.totalPrice
if (!total.local.currency) {
total.local.currency = room.currencyCode
@@ -93,17 +82,14 @@ export function calculateTotalPrice(rooms: Room[], currency: CurrencyEnum) {
case PriceTypeEnum.points:
case PriceTypeEnum.voucher:
{
if (room.roomPrice.perStay.local.price || pkgsSum) {
if (room.totalPrice) {
total.local.additionalPrice =
room.roomPrice.perStay.local.price +
pkgsSum.price +
breakfastPrice
(total.local.additionalPrice || 0) + room.totalPrice
}
if (!total.local.additionalPriceCurrency) {
if (room.roomPrice.perStay.local.currency) {
total.local.additionalPriceCurrency =
room.roomPrice.perStay.local.currency
if (room.currencyCode) {
total.local.additionalPriceCurrency = room.currencyCode
} else {
total.local.additionalPriceCurrency = currency
}

View File

@@ -0,0 +1,62 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import Cheques from "./Cheques"
import Points from "./Points"
import Price from "./Price"
import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
interface PriceTypeProps
extends Pick<
BookingConfirmation["booking"],
"cheques" | "rateDefinition" | "roomPoints" | "totalPrice" | "vouchers"
> {
isCancelled: boolean
priceType: PriceTypeEnum
}
export default function PriceType({
cheques,
isCancelled,
priceType,
rateDefinition,
roomPoints,
totalPrice,
vouchers,
}: PriceTypeProps) {
const intl = useIntl()
switch (priceType) {
case PriceTypeEnum.cheque:
return <Cheques cheques={cheques} price={isCancelled ? 0 : totalPrice} />
case PriceTypeEnum.money:
return (
<Price
isMember={rateDefinition.isMemberRate}
price={isCancelled ? 0 : totalPrice}
variant="Title/Subtitle/lg"
/>
)
case PriceTypeEnum.points:
return <Points points={roomPoints} variant="Title/Subtitle/lg" />
case PriceTypeEnum.voucher:
return (
<Typography variant="Title/Subtitle/lg">
<p>
{intl.formatMessage(
{
defaultMessage: "{count} voucher",
},
{ count: vouchers }
)}
</p>
</Typography>
)
default:
return null
}
}

View File

@@ -17,9 +17,8 @@ import useLang from "@/hooks/useLang"
import { formatPrice } from "@/utils/numberFormatting"
import GuestDetails from "../GuestDetails"
import Points from "../Points"
import Price from "../Price"
import PriceDetails from "../PriceDetails"
import PriceType from "../PriceType"
import { hasModifiableRate } from "../utils"
import ToggleSidePeek from "./ToggleSidePeek"
@@ -58,16 +57,17 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
const {
adults,
bookingCode,
breakfast,
cheques,
childrenAges,
confirmationNumber,
bookingCode,
roomPrice,
roomPoints,
breakfast,
packages,
rateDefinition,
isCancelled,
packages,
priceType,
rateDefinition,
roomPoints,
totalPrice,
vouchers,
} = bookedRoom
@@ -419,26 +419,15 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
})}
</p>
</Typography>
{priceType === "points" ? (
<Points points={roomPoints} variant="Title/Subtitle/lg" />
) : priceType === "voucher" ? (
<Typography variant="Title/Subtitle/lg">
<p>
{intl.formatMessage(
{
defaultMessage: "{count} voucher",
},
{ count: vouchers }
)}
</p>
</Typography>
) : (
<Price
price={isCancelled ? 0 : roomPrice.perStay.local.price}
variant="Title/Subtitle/lg"
isMember={rateDefinition.isMemberRate}
/>
)}
<PriceType
cheques={cheques}
isCancelled={isCancelled}
priceType={priceType}
rateDefinition={rateDefinition}
roomPoints={roomPoints}
totalPrice={totalPrice}
vouchers={vouchers}
/>
</div>
</div>
</div>

View File

@@ -1,12 +1,12 @@
import type { PriceType } from "@/types/components/hotelReservation/myStay/myStay"
import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
export function getPriceType(
cheques: number,
points: number,
vouchers: number
): PriceType {
if (points > 0) return "points"
if (vouchers > 0) return "voucher"
if (cheques > 0) return "cheque"
return "money"
): PriceTypeEnum {
if (points > 0) return PriceTypeEnum.points
if (vouchers > 0) return PriceTypeEnum.voucher
if (cheques > 0) return PriceTypeEnum.cheque
return PriceTypeEnum.money
}

View File

@@ -153,6 +153,9 @@ export function mapRoomDetails({
requested: undefined,
},
},
totalPriceExVat: booking.totalPriceExVat,
totalPrice: booking.totalPrice,
vatAmount: booking.vatAmount,
breakfast,
priceType,
}