feat: bedtypes is selectable again

This commit is contained in:
Simon Emanuelsson
2025-04-07 13:43:52 +02:00
committed by Michael Zetterberg
parent f62723c6e5
commit afb37d0cc5
69 changed files with 2135 additions and 2349 deletions

View File

@@ -3,6 +3,7 @@ import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { dt } from "@/lib/dt"
import { useRatesStore } from "@/stores/select-rate"
import Image from "@/components/Image"
@@ -15,22 +16,31 @@ import { useRoomContext } from "@/contexts/SelectRate/Room"
import styles from "./selectedRoomPanel.module.css"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { CurrencyEnum } from "@/types/enums/currency"
import { RateEnum } from "@/types/enums/rate"
export default function SelectedRoomPanel() {
const intl = useIntl()
const { isUserLoggedIn, roomCategories, rooms } = useRatesStore((state) => ({
isUserLoggedIn: state.isUserLoggedIn,
roomCategories: state.roomCategories,
rooms: state.rooms,
}))
const { dates, isUserLoggedIn, roomCategories, rooms } = useRatesStore(
(state) => ({
dates: {
from: state.booking.fromDate,
to: state.booking.toDate,
},
isUserLoggedIn: state.isUserLoggedIn,
roomCategories: state.roomCategories,
rooms: state.rooms,
})
)
const {
actions: { modifyRate },
isMainRoom,
roomNr,
selectedPackages,
selectedRate,
} = useRoomContext()
const nights = dt(dates.to).diff(dt(dates.from), "days")
const images = roomCategories.find((roomCategory) =>
roomCategory.roomTypes.some(
@@ -60,8 +70,16 @@ export default function SelectedRoomPanel() {
return null
}
let petRoomPrice = 0
const petRoomPackageSelected = selectedPackages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
)
if (petRoomPackageSelected) {
petRoomPrice = petRoomPackageSelected.localPrice.totalPrice / nights
}
const night = intl.formatMessage({ id: "night" })
let selectedProduct
let isPerNight = true
if (
isUserLoggedIn &&
isMainRoom &&
@@ -69,19 +87,17 @@ export default function SelectedRoomPanel() {
selectedRate.product.member
) {
const { localPrice } = selectedRate.product.member
selectedProduct = `${localPrice.pricePerNight} ${localPrice.currency}`
selectedProduct = `${localPrice.pricePerNight + petRoomPrice} ${localPrice.currency} / ${night}`
} else if ("public" in selectedRate.product && selectedRate.product.public) {
const { localPrice } = selectedRate.product.public
selectedProduct = `${localPrice.pricePerNight} ${localPrice.currency}`
selectedProduct = `${localPrice.pricePerNight + petRoomPrice} ${localPrice.currency} / ${night}`
} else if ("corporateCheque" in selectedRate.product) {
isPerNight = false
const { localPrice } = selectedRate.product.corporateCheque
selectedProduct = `${localPrice.numberOfCheques} ${CurrencyEnum.CC}`
if (localPrice.additionalPricePerStay && localPrice.currency) {
selectedProduct = `${selectedProduct} + ${localPrice.additionalPricePerStay} ${localPrice.currency}`
}
} else if ("voucher" in selectedRate.product) {
isPerNight = false
selectedProduct = `${selectedRate.product.voucher.numberOfVouchers} ${CurrencyEnum.Voucher}`
}
@@ -109,9 +125,7 @@ export default function SelectedRoomPanel() {
<Body color="uiTextMediumContrast">
{getRateTitle(selectedRate.product.rate)}
</Body>
<Body color="uiTextHighContrast">
{`${selectedProduct}${isPerNight ? "/" + intl.formatMessage({ id: "night" }) : ""}`}
</Body>
<Body color="uiTextHighContrast">{selectedProduct}</Body>
</div>
<div className={styles.imageContainer}>
{images?.[0]?.imageSizes?.tiny ? (

View File

@@ -17,12 +17,16 @@ export default function NoAvailabilityAlert() {
const lang = useLang()
const intl = useIntl()
const bookingCode = useRatesStore((state) => state.booking.bookingCode)
const { rooms } = useRoomContext()
const { isFetchingPackages, rooms } = useRoomContext()
const noAvailableRooms = rooms.every(
(roomConfig) => roomConfig.status === AvailabilityEnum.NotAvailable
)
if (isFetchingPackages) {
return null
}
if (noAvailableRooms) {
const text = intl.formatMessage({
id: "There are no rooms available that match your request.",

View File

@@ -1,54 +0,0 @@
"use client"
import { Checkbox as AriaCheckbox } from "react-aria-components"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./checkbox.module.css"
import type { MaterialSymbolProps } from "react-material-symbols"
interface CheckboxProps {
name: string
value: string
isSelected: boolean
iconName: MaterialSymbolProps["icon"]
isDisabled: boolean
onChange: (value: string) => void
}
export default function Checkbox({
isSelected,
name,
value,
iconName,
isDisabled,
onChange,
}: CheckboxProps) {
return (
<AriaCheckbox
className={styles.checkboxWrapper}
isSelected={isSelected}
isDisabled={isDisabled}
onChange={() => onChange(value)}
>
{({ isSelected }) => (
<>
<span className={styles.checkbox}>
{isSelected && <MaterialIcon icon="check" color="Icon/Inverted" />}
</span>
<Typography
variant="Body/Paragraph/mdRegular"
className={styles.text}
>
<span>{name}</span>
</Typography>
{iconName ? (
<MaterialIcon icon={iconName} color="Icon/Default" />
) : null}
</>
)}
</AriaCheckbox>
)
}

View File

@@ -1,196 +0,0 @@
"use client"
import { useEffect, useState } from "react"
import {
Button as AriaButton,
Dialog,
DialogTrigger,
Popover,
} from "react-aria-components"
import { Controller, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { ChipButton } from "@scandic-hotels/design-system/ChipButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useRatesStore } from "@/stores/select-rate"
import Divider from "@/components/TempDesignSystem/Divider"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import Checkbox from "./Checkbox"
import { getIconNameByPackageCode } from "./utils"
import styles from "./roomPackageFilter.module.css"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
type FormValues = {
selectedPackages: RoomPackageCodeEnum[]
}
export default function RoomPackageFilter() {
const intl = useIntl()
const [isOpen, setIsOpen] = useState(false)
const packageOptions = useRatesStore((state) => state.packageOptions)
const {
actions: { togglePackages },
selectedPackages,
} = useRoomContext()
const { setValue, handleSubmit, control } = useForm<FormValues>({
defaultValues: {
selectedPackages: selectedPackages,
},
})
useEffect(() => {
setValue("selectedPackages", selectedPackages)
}, [selectedPackages, setValue])
function onSubmit(data: FormValues) {
togglePackages(data.selectedPackages)
setIsOpen(false)
}
return (
<div className={styles.roomPackageFilter}>
{selectedPackages.map((pkg) => (
<AriaButton
key={pkg}
onPress={() => {
const packages = selectedPackages.filter((s) => s !== pkg)
togglePackages(packages)
}}
className={styles.activeFilterButton}
>
<MaterialIcon
icon={getIconNameByPackageCode(pkg)}
size={16}
color="Icon/Interactive/Default"
/>
<MaterialIcon
icon="close"
size={16}
color="Icon/Interactive/Default"
/>
</AriaButton>
))}
<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
<ChipButton variant="Outlined">
{intl.formatMessage({ id: "Room preferences" })}
<MaterialIcon
icon="keyboard_arrow_down"
size={20}
color="CurrentColor"
/>
</ChipButton>
<Popover placement="bottom end">
<Dialog className={styles.dialog}>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="selectedPackages"
render={({ field }) => (
<div>
{packageOptions.map((option) => {
const isPetRoom =
option.code === RoomPackageCodeEnum.PET_ROOM
const isAllergyRoom =
option.code === RoomPackageCodeEnum.ALLERGY_ROOM
const hasPetRoom = field.value.includes(
RoomPackageCodeEnum.PET_ROOM
)
const hasAllergyRoom = field.value.includes(
RoomPackageCodeEnum.ALLERGY_ROOM
)
const isDisabled =
(isPetRoom && hasAllergyRoom) ||
(isAllergyRoom && hasPetRoom)
return (
<>
<Checkbox
key={option.code}
name={option.description}
value={option.code}
iconName={getIconNameByPackageCode(option.code)}
isSelected={field.value.includes(option.code)}
isDisabled={isDisabled}
onChange={() => {
const isSelected = field.value.includes(
option.code
)
const newValue = isSelected
? field.value.filter(
(pkg) => pkg !== option.code
)
: [...field.value, option.code]
field.onChange(newValue)
}}
/>
{option.code === RoomPackageCodeEnum.PET_ROOM && (
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={styles.additionalInformation}>
{intl.formatMessage(
{
id: "<b>200 SEK/night</b> Important information on pricing and features of pet-friendly rooms.",
},
{
b: (str) => (
<Typography variant="Body/Supporting text (caption)/smBold">
<span
className={
styles.additionalInformationPrice
}
>
{str}
</span>
</Typography>
),
}
)}
</p>
</Typography>
)}
</>
)
})}
</div>
)}
/>
<div className={styles.footer}>
<Divider color="borderDividerSubtle" />
<div className={styles.buttonContainer}>
<Typography variant="Body/Supporting text (caption)/smBold">
<Button
variant="Text"
size="Small"
onPress={() => {
togglePackages([])
setIsOpen(false)
}}
>
{intl.formatMessage({ id: "Clear" })}
</Button>
</Typography>
<Typography variant="Body/Supporting text (caption)/smBold">
<Button variant="Tertiary" size="Small" type="submit">
{intl.formatMessage({ id: "Apply" })}
</Button>
</Typography>
</div>
</div>
</form>
</Dialog>
</Popover>
</DialogTrigger>
</div>
)
}

View File

@@ -2,10 +2,12 @@
import { useIntl } from "react-intl"
import { trpc } from "@/lib/trpc/client"
import { useRatesStore } from "@/stores/select-rate"
import Select from "@/components/TempDesignSystem/Select"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import useLang from "@/hooks/useLang"
import styles from "./bookingCodeFilter.module.css"
@@ -16,12 +18,16 @@ import { RateTypeEnum } from "@/types/enums/rateType"
export default function BookingCodeFilter() {
const intl = useIntl()
const lang = useLang()
const utils = trpc.useUtils()
const {
actions: { selectFilter },
selectedFilter,
actions: { appendRegularRates, selectFilter },
bookingRoom,
rooms,
selectedFilter,
selectedPackages,
} = useRoomContext()
const bookingCode = useRatesStore((state) => state.booking.bookingCode)
const booking = useRatesStore((state) => state.booking)
const bookingCodeFilterItems = [
{
@@ -38,25 +44,45 @@ export default function BookingCodeFilter() {
},
]
function handleChangeFilter(selectedFilter: Key) {
async function handleChangeFilter(selectedFilter: Key) {
selectFilter(selectedFilter as BookingCodeFilterEnum)
const room = await utils.hotel.availability.selectRate.room.fetch({
booking: {
...booking,
room: {
...bookingRoom,
bookingCode:
selectedFilter === BookingCodeFilterEnum.Discounted
? booking.bookingCode
: undefined,
packages: selectedPackages.map((pkg) => pkg.code),
},
},
lang,
})
appendRegularRates(room?.roomConfigurations)
}
const hideFilterDespiteBookingCode = rooms.every((room) =>
room.products.every((product) => {
const isRedemption = Array.isArray(product)
if (isRedemption) {
return true
}
const isCorporateCheque =
product.rateDefinition?.rateType === RateTypeEnum.CorporateCheque
const isVoucher =
product.rateDefinition?.rateType === RateTypeEnum.Voucher
return isCorporateCheque || isVoucher
})
)
const hideFilterDespiteBookingCode =
rooms.length &&
rooms.every((room) =>
room.products.every((product) => {
const isRedemption = Array.isArray(product)
if (isRedemption) {
return true
}
const isCorporateCheque =
product.rateDefinition?.rateType === RateTypeEnum.CorporateCheque
const isVoucher =
product.rateDefinition?.rateType === RateTypeEnum.Voucher
return isCorporateCheque || isVoucher
})
)
if ((bookingCode && hideFilterDespiteBookingCode) || !bookingCode) {
if (
(booking.bookingCode && hideFilterDespiteBookingCode) ||
!booking.bookingCode
) {
return null
}

View File

@@ -0,0 +1,40 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import { formatPrice } from "@/utils/numberFormatting"
import styles from "./petRoom.module.css"
export default function PetRoomMessage() {
const intl = useIntl()
const { petRoomPackage } = useRoomContext()
if (!petRoomPackage) {
return null
}
return (
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={styles.additionalInformation}>
{intl.formatMessage(
{
id: "Pet-friendly rooms include a charge of approx. <b>{price}/stay</b>",
},
{
b: (str) => (
<Typography variant="Body/Supporting text (caption)/smBold">
<span className={styles.additionalInformationPrice}>{str}</span>
</Typography>
),
price: formatPrice(
intl,
petRoomPackage.localPrice.price,
petRoomPackage.localPrice.currency
),
}
)}
</p>
</Typography>
)
}

View File

@@ -0,0 +1,8 @@
.additionalInformation {
color: var(--Text-Tertiary);
padding: var(--Space-x1) var(--Space-x15);
}
.additionalInformationPrice {
color: var(--Text-Default);
}

View File

@@ -0,0 +1,80 @@
"use client"
import { Fragment } from "react"
import { Checkbox, CheckboxGroup } from "react-aria-components"
import { Controller, useFormContext } from "react-hook-form"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useRatesStore } from "@/stores/select-rate"
import { getIconNameByPackageCode } from "../../utils"
import PetRoomMessage from "./PetRoomMessage"
import {
checkIsAllergyRoom,
checkIsPetRoom,
includesAllergyRoom,
includesPetRoom,
} from "./utils"
import styles from "./checkbox.module.css"
import type { FormValues } from "../formValues"
export default function Checkboxes() {
const packageOptions = useRatesStore((state) => state.packageOptions)
const { control } = useFormContext<FormValues>()
return (
<Controller
control={control}
name="selectedPackages"
render={({ field }) => {
const allergyRoomSelected = includesAllergyRoom(field.value)
const petRoomSelected = includesPetRoom(field.value)
return (
<CheckboxGroup {...field}>
<div>
{packageOptions.map((option) => {
const isAllergyRoom = checkIsAllergyRoom(option.code)
const isPetRoom = checkIsPetRoom(option.code)
const isDisabled =
(isPetRoom && allergyRoomSelected) ||
(isAllergyRoom && petRoomSelected)
const isSelected = field.value.includes(option.code)
const iconName = getIconNameByPackageCode(option.code)
return (
<Fragment key={option.code}>
<Checkbox
key={option.code}
className={styles.checkboxWrapper}
isDisabled={isDisabled}
value={option.code}
>
<span className={styles.checkbox}>
{isSelected ? (
<MaterialIcon icon="check" color="Icon/Inverted" />
) : null}
</span>
<Typography
className={styles.text}
variant="Body/Paragraph/mdRegular"
>
<span>{option.description}</span>
</Typography>
{iconName ? (
<MaterialIcon icon={iconName} color="Icon/Default" />
) : null}
</Checkbox>
{isPetRoom ? <PetRoomMessage /> : null}
</Fragment>
)
})}
</div>
</CheckboxGroup>
)
}}
/>
)
}

View File

@@ -0,0 +1,18 @@
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { PackageEnum } from "@/types/requests/packages"
export function includesAllergyRoom(codes: PackageEnum[]) {
return codes.includes(RoomPackageCodeEnum.ALLERGY_ROOM)
}
export function includesPetRoom(codes: PackageEnum[]) {
return codes.includes(RoomPackageCodeEnum.PET_ROOM)
}
export function checkIsAllergyRoom(code: PackageEnum) {
return code === RoomPackageCodeEnum.ALLERGY_ROOM
}
export function checkIsPetRoom(code: PackageEnum) {
return code === RoomPackageCodeEnum.PET_ROOM
}

View File

@@ -0,0 +1,11 @@
.footer {
display: grid;
gap: var(--Space-x1);
padding: 0 var(--Space-x15);
}
.buttonContainer {
align-items: center;
display: flex;
justify-content: space-between;
}

View File

@@ -0,0 +1,5 @@
import type { PackageEnum } from "@/types/requests/packages"
export type FormValues = {
selectedPackages: PackageEnum[]
}

View File

@@ -0,0 +1,98 @@
"use client"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { trpc } from "@/lib/trpc/client"
import { useRatesStore } from "@/stores/select-rate"
import Divider from "@/components/TempDesignSystem/Divider"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import useLang from "@/hooks/useLang"
import Checkboxes from "./Checkboxes"
import styles from "./form.module.css"
import type { PackageEnum } from "@/types/requests/packages"
import type { FormValues } from "./formValues"
export default function Form({ close }: { close: VoidFunction }) {
const intl = useIntl()
const lang = useLang()
const utils = trpc.useUtils()
const {
actions: { removeSelectedPackages, selectPackages, updateRooms },
bookingRoom,
selectedPackages,
} = useRoomContext()
const booking = useRatesStore((state) => state.booking)
const methods = useForm<FormValues>({
values: {
selectedPackages: selectedPackages.map((pkg) => pkg.code),
},
})
async function getFilteredRates(packages: PackageEnum[]) {
const filterRates = await utils.hotel.availability.selectRate.room.fetch({
booking: {
fromDate: booking.fromDate,
hotelId: booking.hotelId,
searchType: booking.searchType,
toDate: booking.toDate,
room: {
...bookingRoom,
bookingCode: bookingRoom.rateCode
? bookingRoom.bookingCode
: booking.bookingCode,
packages,
},
},
lang,
})
updateRooms(filterRates?.roomConfigurations)
}
function clearSelectedPackages() {
removeSelectedPackages()
close()
getFilteredRates([])
}
function onSubmit(data: FormValues) {
selectPackages(data.selectedPackages)
close()
getFilteredRates(data.selectedPackages)
}
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<Checkboxes />
<div className={styles.footer}>
<Divider color="borderDividerSubtle" />
<div className={styles.buttonContainer}>
<Typography variant="Body/Supporting text (caption)/smBold">
<Button
onPress={clearSelectedPackages}
size="Small"
variant="Text"
>
{intl.formatMessage({ id: "Clear" })}
</Button>
</Typography>
<Typography variant="Body/Supporting text (caption)/smBold">
<Button variant="Tertiary" size="Small" type="submit">
{intl.formatMessage({ id: "Apply" })}
</Button>
</Typography>
</div>
</div>
</form>
</FormProvider>
)
}

View File

@@ -0,0 +1,101 @@
"use client"
import { useState } from "react"
import {
Button as AriaButton,
Dialog,
DialogTrigger,
Popover,
} from "react-aria-components"
import { useIntl } from "react-intl"
import { ChipButton } from "@scandic-hotels/design-system/ChipButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { trpc } from "@/lib/trpc/client"
import { useRatesStore } from "@/stores/select-rate"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import useLang from "@/hooks/useLang"
import Form from "./Form"
import { getIconNameByPackageCode } from "./utils"
import styles from "./roomPackageFilter.module.css"
import type { PackageEnum } from "@/types/requests/packages"
export default function RoomPackageFilter() {
const intl = useIntl()
const lang = useLang()
const utils = trpc.useUtils()
const [isOpen, setIsOpen] = useState(false)
const {
actions: { removeSelectedPackage, updateRooms },
bookingRoom,
selectedPackages,
} = useRoomContext()
const booking = useRatesStore((state) => state.booking)
async function deleteSelectedPackage(code: PackageEnum) {
removeSelectedPackage(code)
const filterRates = await utils.hotel.availability.selectRate.room.fetch({
booking: {
fromDate: booking.fromDate,
hotelId: booking.hotelId,
searchType: booking.searchType,
toDate: booking.toDate,
room: {
...bookingRoom,
bookingCode: bookingRoom.rateCode
? bookingRoom.bookingCode
: booking.bookingCode,
packages: selectedPackages
.filter((pkg) => pkg.code !== code)
.map((pkg) => pkg.code),
},
},
lang,
})
updateRooms(filterRates?.roomConfigurations)
}
return (
<div className={styles.roomPackageFilter}>
{selectedPackages.map((pkg) => (
<AriaButton
key={pkg.code}
className={styles.activeFilterButton}
onPress={() => deleteSelectedPackage(pkg.code)}
>
<MaterialIcon
icon={getIconNameByPackageCode(pkg.code)}
size={16}
color="Icon/Interactive/Default"
/>
<MaterialIcon
icon="close"
size={16}
color="Icon/Interactive/Default"
/>
</AriaButton>
))}
<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
<ChipButton variant="Outlined">
{intl.formatMessage({ id: "Room preferences" })}
<MaterialIcon
icon="keyboard_arrow_down"
size={20}
color="CurrentColor"
/>
</ChipButton>
<Popover placement="bottom end">
<Dialog className={styles.dialog}>
<Form close={() => setIsOpen(false)} />
</Dialog>
</Popover>
</DialogTrigger>
</div>
)
}

View File

@@ -3,33 +3,6 @@
gap: var(--Space-x1);
}
.dialog {
display: grid;
gap: var(--Space-x1);
padding: var(--Space-x2);
flex-direction: column;
align-items: flex-end;
border-radius: var(--Corner-radius-md);
background-color: var(--Surface-Primary-Default);
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
max-width: 340px;
}
.footer {
display: grid;
gap: var(--Space-x1);
padding: 0 var(--Space-x15);
}
.additionalInformation {
color: var(--Text-Tertiary);
padding: var(--Space-x1) var(--Space-x15);
}
.additionalInformationPrice {
color: var(--Text-Default);
}
.activeFilterButton {
display: flex;
justify-content: center;
@@ -42,8 +15,14 @@
cursor: pointer;
}
.buttonContainer {
display: flex;
justify-content: space-between;
align-items: center;
.dialog {
display: grid;
gap: var(--Space-x1);
padding: var(--Space-x2);
flex-direction: column;
align-items: flex-end;
border-radius: var(--Corner-radius-md);
background-color: var(--Surface-Primary-Default);
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
max-width: 340px;
}

View File

@@ -1,10 +1,11 @@
import type { MaterialSymbolProps } from "react-material-symbols"
import type { SymbolCodepoints } from "react-material-symbols"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { PackageEnum } from "@/types/requests/packages"
export function getIconNameByPackageCode(
packageCode: RoomPackageCodeEnum
): MaterialSymbolProps["icon"] {
packageCode: PackageEnum
): SymbolCodepoints {
switch (packageCode) {
case RoomPackageCodeEnum.PET_ROOM:
return "pets"

View File

@@ -5,15 +5,15 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import BookingCodeFilter from "../BookingCodeFilter"
import RoomPackageFilter from "../RoomPackageFilter"
import BookingCodeFilter from "./BookingCodeFilter"
import RoomPackageFilter from "./RoomPackageFilter"
import styles from "./roomsHeader.module.css"
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
export default function RoomsHeader() {
const { rooms, totalRooms } = useRoomContext()
const { isFetchingPackages, rooms, totalRooms } = useRoomContext()
const intl = useIntl()
const availableRooms = rooms.filter(
@@ -42,11 +42,15 @@ export default function RoomsHeader() {
return (
<div className={styles.container}>
<Typography variant="Title/Subtitle/md" className={styles.availableRooms}>
<p>
{availableRooms !== totalRooms
? notAllRoomsAvailableText
: allRoomsAvailableText}
</p>
{isFetchingPackages ? (
<p></p>
) : (
<p>
{availableRooms !== totalRooms
? notAllRoomsAvailableText
: allRoomsAvailableText}
</p>
)}
</Typography>
<div className={styles.filters}>
<RoomPackageFilter />

View File

@@ -37,24 +37,21 @@ export default function Rates({
selectedFilter,
selectedPackages,
} = useRoomContext()
const { nights, petRoomPackage } = useRatesStore((state) => ({
nights: dt(state.booking.toDate).diff(state.booking.fromDate, "days"),
petRoomPackage: state.petRoomPackage,
}))
const nights = useRatesStore((state) =>
dt(state.booking.toDate).diff(state.booking.fromDate, "days")
)
function handleSelectRate(product: Product) {
selectRate({ features, product, roomType, roomTypeCode })
}
const petRoomPackageSelected = selectedPackages.includes(
RoomPackageCodeEnum.PET_ROOM
const petRoomPackageSelected = selectedPackages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
)
const sharedProps = {
handleSelectRate,
nights,
petRoomPackage:
petRoomPackageSelected && petRoomPackage ? petRoomPackage : undefined,
petRoomPackage: petRoomPackageSelected,
roomTypeCode,
}
const showAllRates = selectedFilter === BookingCodeFilterEnum.All

View File

@@ -1,10 +1,10 @@
import type { RoomPackage } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { Package } from "@/types/requests/packages"
export function calculatePricePerNightPriceProduct(
pricePerNight: number,
requestedPricePerNight: number | undefined,
nights: number,
petRoomPackage?: RoomPackage
petRoomPackage?: Package
) {
const totalPrice = petRoomPackage?.localPrice
? Math.floor(pricePerNight + petRoomPackage.localPrice.price / nights)

View File

@@ -14,7 +14,7 @@ import styles from "./image.module.css"
import type { RoomListItemImageProps } from "@/types/components/hotelReservation/selectRate/roomListItem"
export default function RoomImage({
features,
roomPackages,
roomsLeft,
roomType,
roomTypeCode,
@@ -44,11 +44,13 @@ export default function RoomImage({
</Footnote>
</span>
) : null}
{features
.filter((feature) => selectedPackages.includes(feature.code))
.map((feature) => (
<span className={styles.chip} key={feature.code}>
{IconForFeatureCode({ featureCode: feature.code, size: 16 })}
{roomPackages
.filter((pkg) =>
selectedPackages.find((spkg) => spkg.code === pkg.code)
)
.map((pkg) => (
<span className={styles.chip} key={pkg.code}>
{IconForFeatureCode({ featureCode: pkg.code, size: 16 })}
</span>
))}
</div>

View File

@@ -1,5 +1,7 @@
"use client"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import Details from "./Details"
import { listItemVariants } from "./listItemVariants"
import Rates from "./Rates"
@@ -12,6 +14,7 @@ import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHote
import type { RoomListItemProps } from "@/types/components/hotelReservation/selectRate/roomListItem"
export default function RoomListItem({ roomConfiguration }: RoomListItemProps) {
const { roomPackages } = useRoomContext()
const classNames = listItemVariants({
availability:
roomConfiguration.status === AvailabilityEnum.NotAvailable
@@ -22,7 +25,7 @@ export default function RoomListItem({ roomConfiguration }: RoomListItemProps) {
return (
<li className={classNames}>
<RoomImage
features={roomConfiguration.features}
roomPackages={roomPackages}
roomType={roomConfiguration.roomType}
roomTypeCode={roomConfiguration.roomTypeCode}
roomsLeft={roomConfiguration.roomsLeft}

View File

@@ -8,12 +8,10 @@ import ScrollToList from "./ScrollToList"
import styles from "./rooms.module.css"
export default function RoomsList() {
const { rooms, isFetchingRoomFeatures } = useRoomContext()
if (isFetchingRoomFeatures) {
const { isFetchingPackages, rooms } = useRoomContext()
if (isFetchingPackages) {
return <RoomsListSkeleton />
}
return (
<>
<ScrollToList />