feat: add Price details modal

This commit is contained in:
Arvid Norlin
2024-12-04 16:40:45 +01:00
parent df1e4da001
commit 0c7c6ea21a
24 changed files with 382 additions and 60 deletions

View File

@@ -21,4 +21,4 @@
.modalContent {
width: 352px;
}
}
}

View File

@@ -50,11 +50,11 @@ export default function SpecialRequests() {
noPreferenceItem,
{
value: FloorPreference.HIGH,
label: intl.formatMessage({ id: "High level" }),
label: intl.formatMessage({ id: "High floor" }),
},
{
value: FloorPreference.LOW,
label: intl.formatMessage({ id: "Low level" }),
label: intl.formatMessage({ id: "Low floor" }),
},
]}
/>

View File

@@ -9,8 +9,8 @@ const stringMatcher =
const isValidString = (key: string) => stringMatcher.test(key)
export enum FloorPreference {
LOW = "Low level",
HIGH = "High level",
LOW = "Low floor",
HIGH = "High floor",
}
export enum ElevatorPreference {

View File

@@ -22,7 +22,6 @@
.dialog {
display: flex;
flex-direction: column;
padding-bottom: var(--Spacing-x3);
/* for supporting animations within content */
position: relative;
@@ -37,8 +36,7 @@
align-items: center;
height: var(--button-dimension);
position: relative;
justify-content: center;
padding: var(--Spacing-x3) var(--Spacing-x2) 0;
padding: var(--Spacing-x2) var(--Spacing-x3) 0;
}
.content {
@@ -74,4 +72,4 @@
width: auto;
border-radius: var(--Corner-radius-Medium);
}
}
}

View File

@@ -0,0 +1,187 @@
"use client"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import { useEnterDetailsStore } from "@/stores/enter-details"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import useLang from "@/hooks/useLang"
import { getNights } from "@/utils/dateFormatting"
import styles from "./priceDetailsTable.module.css"
import { type PriceDetailsTableProps } from "@/types/components/hotelReservation/enterDetails/priceDetailsTable"
import type { DetailsState } from "@/types/stores/enter-details"
function Row({ label, value }: { label: string; value: string }) {
return (
<tr className={styles.row}>
<td>
<Caption>{label}</Caption>
</td>
<td className={styles.price}>
<Caption>{value}</Caption>
</td>
</tr>
)
}
function TableSection({ children }: React.PropsWithChildren) {
return <tbody className={styles.tableSection}>{children}</tbody>
}
function TableSectionHeader({ title }: { title: string }) {
return (
<tr>
<th colSpan={2}>
<Body>{title}</Body>
</th>
</tr>
)
}
export function storeSelector(state: DetailsState) {
return {
bedType: state.bedType,
booking: state.booking,
breakfast: state.breakfast,
join: state.guest.join,
membershipNo: state.guest.membershipNo,
packages: state.packages,
roomRate: state.roomRate,
roomPrice: state.roomPrice,
toggleSummaryOpen: state.actions.toggleSummaryOpen,
togglePriceDetailsModalOpen: state.actions.togglePriceDetailsModalOpen,
totalPrice: state.totalPrice,
vat: state.vat,
}
}
export default function PriceDetailsTable({
roomType,
}: PriceDetailsTableProps) {
const intl = useIntl()
const lang = useLang()
const {
bedType,
booking,
breakfast,
join,
membershipNo,
packages,
roomPrice,
roomRate,
toggleSummaryOpen,
togglePriceDetailsModalOpen,
totalPrice,
vat,
} = useEnterDetailsStore(storeSelector)
// TODO: Update for Multiroom later
const { adults, children } = booking.rooms[0]
const nights = getNights(booking.fromDate, booking.toDate)
const vatPercentage = vat / 100
const vatAmount = totalPrice.local.price * vatPercentage
const priceExclVat = totalPrice.local.price - vatAmount
return (
<table className={styles.priceDetailsTable}>
<TableSection>
<TableSectionHeader title={roomType} />
{nights.map((night) => {
return (
<Row
key={night.format("YYMMDD")}
label={dt(night).locale(lang).format("ddd, D MMM YYYY")}
value={intl.formatNumber(roomPrice.perNight.local.price, {
currency: roomPrice.perNight.local.currency,
style: "currency",
})}
/>
)
})}
</TableSection>
{bedType ? (
<TableSection>
<Row
label={bedType.description}
value={intl.formatNumber(0, {
currency: roomPrice.perStay.local.currency,
style: "currency",
})}
/>
</TableSection>
) : null}
{breakfast ? (
<TableSection>
<TableSectionHeader title={intl.formatMessage({ id: "Breakfast" })} />
<Row
label={intl.formatMessage(
{ id: "booking.adults.breakfasts" },
{ totalAdults: adults, totalBreakfasts: nights.length }
)}
value={intl.formatNumber(
parseInt(breakfast.localPrice.totalPrice),
{
currency: breakfast.localPrice.currency,
style: "currency",
}
)}
/>
{children?.length ? (
<Row
label={intl.formatMessage(
{ id: "booking.children.breakfasts" },
{
totalChildren: children.length,
totalBreakfasts: nights.length,
}
)}
value={intl.formatNumber(0, {
currency: breakfast.localPrice.currency,
style: "currency",
})}
/>
) : null}
</TableSection>
) : null}
<TableSection>
<TableSectionHeader title={intl.formatMessage({ id: "Total" })} />
<Row
label={intl.formatMessage({ id: "booking.vat.excl" })}
value={intl.formatNumber(priceExclVat, {
currency: totalPrice.local.currency,
style: "currency",
})}
/>
<Row
label={intl.formatMessage({ id: "booking.vat" }, { vat })}
value={intl.formatNumber(vatAmount, {
currency: totalPrice.local.currency,
style: "currency",
})}
/>
<tr className={styles.row}>
<td>
<Body textTransform="bold">
{intl.formatMessage({ id: "booking.vat.incl" })}
</Body>
</td>
<td className={styles.price}>
<Body textTransform="bold">
{intl.formatNumber(totalPrice.local.price, {
currency: totalPrice.local.currency,
style: "currency",
})}
</Body>
</td>
</tr>
</TableSection>
</table>
)
}

View File

@@ -0,0 +1,34 @@
.priceDetailsTable {
border-collapse: collapse;
width: 100%;
}
.price {
text-align: end;
}
.tableSection {
display: flex;
gap: var(--Spacing-x-half);
flex-direction: column;
padding-bottom: var(--Spacing-x2);
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);
}
.row {
display: flex;
justify-content: space-between;
}
@media screen and (min-width: 768px) {
.priceDetailsTable {
min-width: 512px;
}
}

View File

@@ -1,4 +1,5 @@
"use client"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
@@ -17,8 +18,10 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang"
import { getNights } from "@/utils/dateFormatting"
import Modal from "../../Modal"
import PriceDetailsTable from "../PriceDetailsTable"
import styles from "./ui.module.css"
@@ -39,6 +42,7 @@ export function storeSelector(state: DetailsState) {
toggleSummaryOpen: state.actions.toggleSummaryOpen,
togglePriceDetailsModalOpen: state.actions.togglePriceDetailsModalOpen,
totalPrice: state.totalPrice,
vat: state.vat,
}
}
@@ -63,8 +67,10 @@ export default function SummaryUI({
toggleSummaryOpen,
togglePriceDetailsModalOpen,
totalPrice,
vat,
} = useEnterDetailsStore(storeSelector)
// TODO: Update for Multiroom later
const adults = booking.rooms[0].adults
const children = booking.rooms[0].children
@@ -90,6 +96,7 @@ export default function SummaryUI({
const memberPrice = roomRate.memberRate
? {
currency: roomRate.memberRate.localPrice.currency,
pricePerNight: roomRate.memberRate.localPrice.pricePerNight,
amount: roomRate.memberRate.localPrice.pricePerStay,
}
: null
@@ -141,8 +148,8 @@ export default function SummaryUI({
<div className={styles.entry}>
<Body color="uiTextHighContrast">{roomType}</Body>
<Body color={showMemberPrice ? "red" : "uiTextHighContrast"}>
{intl.formatNumber(roomPrice.local.price, {
currency: roomPrice.local.currency,
{intl.formatNumber(roomPrice.perStay.local.price, {
currency: roomPrice.perStay.local.currency,
style: "currency",
})}
</Body>
@@ -203,7 +210,7 @@ export default function SummaryUI({
<Body color="uiTextHighContrast">
{intl.formatNumber(0, {
currency: roomPrice.local.currency,
currency: roomPrice.perStay.local.currency,
style: "currency",
})}
</Body>
@@ -221,7 +228,7 @@ export default function SummaryUI({
</div>
<Body color="uiTextHighContrast">
{intl.formatNumber(0, {
currency: roomPrice.local.currency,
currency: roomPrice.perStay.local.currency,
style: "currency",
})}
</Body>
@@ -236,7 +243,7 @@ export default function SummaryUI({
</div>
<Body color="uiTextHighContrast">
{intl.formatNumber(0, {
currency: roomPrice.local.currency,
currency: roomPrice.perStay.local.currency,
style: "currency",
})}
</Body>
@@ -249,7 +256,7 @@ export default function SummaryUI({
</Body>
<Body color="uiTextHighContrast">
{intl.formatNumber(0, {
currency: roomPrice.local.currency,
currency: roomPrice.perStay.local.currency,
style: "currency",
})}
</Body>
@@ -303,7 +310,9 @@ export default function SummaryUI({
{ b: (str) => <b>{str}</b> }
)}
</Body>
<Modal
title={intl.formatMessage({ id: "Price details" })}
trigger={
<Button intent="text" onPress={handleTogglePriceDetailsModal}>
<Caption color="burgundy">
@@ -317,13 +326,7 @@ export default function SummaryUI({
</Button>
}
>
<div className={styles.modalContent}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu
risus quis varius quam quisque id diam vel. Rhoncus urna neque
viverra justo. Mattis aliquam faucibus purus in massa. Id cursus
metus aliquam eleifend mi in nulla posuere.
</div>
<PriceDetailsTable roomType={roomType} />
</Modal>
</div>
<div>