feat(SW-2944): Filtering roomTypes with CustomOccuppancy type to only show the biggest
Approved-by: Matilda Landström
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import { getBedIconName } from "@/components/SidePeeks/RoomSidePeek/bedIcon"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import { getBedDescriptionText } from "../utils"
|
||||||
|
import getFilteredRoomTypes from "./utils"
|
||||||
|
|
||||||
|
import styles from "./roomTypes.module.css"
|
||||||
|
|
||||||
|
import type { Room } from "@/types/hotel"
|
||||||
|
|
||||||
|
interface RoomFacilitiesProps {
|
||||||
|
roomTypes: Room["roomTypes"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function RoomTypes({ roomTypes }: RoomFacilitiesProps) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
|
||||||
|
const filteredRoomTypes = getFilteredRoomTypes(roomTypes)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.intro}>
|
||||||
|
<Typography variant="Title/Subtitle/md">
|
||||||
|
<h3>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Bed options",
|
||||||
|
})}
|
||||||
|
</h3>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Based on availability",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<ul className={styles.list}>
|
||||||
|
{filteredRoomTypes.map((roomType) => {
|
||||||
|
const iconName = getBedIconName(roomType.mainBed.type)
|
||||||
|
const descriptionText = getBedDescriptionText(intl, roomType.mainBed)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={roomType.code} className={styles.listItem}>
|
||||||
|
<MaterialIcon
|
||||||
|
color="CurrentColor"
|
||||||
|
icon={iconName}
|
||||||
|
className={styles.icon}
|
||||||
|
/>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<span>{descriptionText}</span>
|
||||||
|
</Typography>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.intro {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Space-x05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
list-style: none;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Space-x05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.listItem {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Space-x1);
|
||||||
|
color: var(--Text-Secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import type { Room } from "@/types/hotel"
|
||||||
|
|
||||||
|
type RoomType = Room["roomTypes"][number]
|
||||||
|
|
||||||
|
// This util function filters room types to show only unique bed configurations
|
||||||
|
export default function getFilteredRoomTypes(roomTypes: RoomType[]) {
|
||||||
|
const customOccupancyRoomTypes = roomTypes.filter(
|
||||||
|
(roomType) => roomType.mainBed.type === "CustomOccupancy"
|
||||||
|
)
|
||||||
|
const otherRoomTypes = roomTypes.filter(
|
||||||
|
(roomType) => roomType.mainBed.type !== "CustomOccupancy"
|
||||||
|
)
|
||||||
|
|
||||||
|
const filteredOtherRoomTypes = filterDuplicatesByDescription(otherRoomTypes)
|
||||||
|
|
||||||
|
// If there are no CustomOccupancy room types, return just the filtered other types
|
||||||
|
if (customOccupancyRoomTypes.length === 0) {
|
||||||
|
return filteredOtherRoomTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is only one CustomOccupancy room type, no need to find the best one
|
||||||
|
if (customOccupancyRoomTypes.length === 1) {
|
||||||
|
return [...filteredOtherRoomTypes, customOccupancyRoomTypes[0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the CustomOccupancy room type with highest occupancy
|
||||||
|
const bestCustomOccupancyRoomType = customOccupancyRoomTypes.reduce<RoomType>(
|
||||||
|
(best, current) => {
|
||||||
|
return current.occupancy.total > best.occupancy.total ? current : best
|
||||||
|
},
|
||||||
|
customOccupancyRoomTypes[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
return [...filteredOtherRoomTypes, bestCustomOccupancyRoomType]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to just filter duplicates by description
|
||||||
|
function filterDuplicatesByDescription(roomTypes: RoomType[]): RoomType[] {
|
||||||
|
const uniqueRoomsByDescription: Record<string, RoomType> = {}
|
||||||
|
|
||||||
|
roomTypes.forEach((roomType) => {
|
||||||
|
const description = roomType.mainBed.description
|
||||||
|
if (!uniqueRoomsByDescription[description]) {
|
||||||
|
uniqueRoomsByDescription[description] = roomType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Object.values(uniqueRoomsByDescription)
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { selectRateWithParams } from "@/constants/routes/hotelReservation"
|
import { selectRateWithParams } from "@/constants/routes/hotelReservation"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
import ImageGallery from "@/components/ImageGallery"
|
import ImageGallery from "@/components/ImageGallery"
|
||||||
import { getBedIconName } from "@/components/SidePeeks/RoomSidePeek/bedIcon"
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
@@ -16,7 +14,7 @@ import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
|||||||
|
|
||||||
import { getRoomNameAsParam } from "../../utils"
|
import { getRoomNameAsParam } from "../../utils"
|
||||||
import RoomFacilities from "./RoomFacilities"
|
import RoomFacilities from "./RoomFacilities"
|
||||||
import { getBedDescriptionText } from "./utils"
|
import RoomTypes from "./RoomTypes"
|
||||||
|
|
||||||
import styles from "./room.module.css"
|
import styles from "./room.module.css"
|
||||||
|
|
||||||
@@ -28,7 +26,15 @@ export default async function RoomSidePeek({
|
|||||||
}: RoomSidePeekProps) {
|
}: RoomSidePeekProps) {
|
||||||
const lang = await getLang()
|
const lang = await getLang()
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
const { roomSize, totalOccupancy, descriptions, images } = room
|
const {
|
||||||
|
roomSize,
|
||||||
|
roomTypes,
|
||||||
|
roomFacilities,
|
||||||
|
name,
|
||||||
|
totalOccupancy,
|
||||||
|
descriptions,
|
||||||
|
images,
|
||||||
|
} = room
|
||||||
const roomDescription = descriptions.medium
|
const roomDescription = descriptions.medium
|
||||||
|
|
||||||
const galleryImages = mapApiImagesToGalleryImages(images)
|
const galleryImages = mapApiImagesToGalleryImages(images)
|
||||||
@@ -36,18 +42,13 @@ export default async function RoomSidePeek({
|
|||||||
const fromdate = dt().format("YYYY-MM-DD")
|
const fromdate = dt().format("YYYY-MM-DD")
|
||||||
const todate = dt().add(1, "day").format("YYYY-MM-DD")
|
const todate = dt().add(1, "day").format("YYYY-MM-DD")
|
||||||
const selectRateURL = selectRateWithParams(lang, hotelId, fromdate, todate)
|
const selectRateURL = selectRateWithParams(lang, hotelId, fromdate, todate)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidePeek
|
<SidePeek contentKey={`room-${getRoomNameAsParam(name)}`} title={name}>
|
||||||
contentKey={`room-${getRoomNameAsParam(room.name)}`}
|
|
||||||
title={room.name}
|
|
||||||
>
|
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.innerContent}>
|
<div className={styles.innerContent}>
|
||||||
<Typography
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
variant="Body/Paragraph/mdRegular"
|
<p className={styles.guests}>
|
||||||
className={styles.guests}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
@@ -83,7 +84,7 @@ export default async function RoomSidePeek({
|
|||||||
<div className={styles.imageContainer}>
|
<div className={styles.imageContainer}>
|
||||||
<ImageGallery
|
<ImageGallery
|
||||||
images={galleryImages}
|
images={galleryImages}
|
||||||
title={room.name}
|
title={name}
|
||||||
height={280}
|
height={280}
|
||||||
hideLabel={true}
|
hideLabel={true}
|
||||||
/>
|
/>
|
||||||
@@ -94,48 +95,11 @@ export default async function RoomSidePeek({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.innerContent}>
|
<div className={styles.innerContent}>
|
||||||
<RoomFacilities roomFacilities={room.roomFacilities} />
|
<RoomFacilities roomFacilities={roomFacilities} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.innerContent}>
|
<div className={styles.innerContent}>
|
||||||
<div className={styles.bedOptions}>
|
<RoomTypes roomTypes={roomTypes} />
|
||||||
<Typography variant="Title/Subtitle/md">
|
|
||||||
<h3>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Bed options",
|
|
||||||
})}
|
|
||||||
</h3>
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<p>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Based on availability",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
<ul className={styles.bedOptions}>
|
|
||||||
{room.roomTypes.map((roomType) => {
|
|
||||||
const iconName = getBedIconName(roomType.mainBed.type)
|
|
||||||
const descriptionText = getBedDescriptionText(
|
|
||||||
intl,
|
|
||||||
roomType.mainBed
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={roomType.code} className={styles.listItem}>
|
|
||||||
<MaterialIcon
|
|
||||||
color="CurrentColor"
|
|
||||||
icon={iconName}
|
|
||||||
className={styles.icon}
|
|
||||||
/>
|
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
|
||||||
<span>{descriptionText}</span>
|
|
||||||
</Typography>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
|
|||||||
@@ -12,39 +12,16 @@
|
|||||||
gap: var(--Spacing-x-one-and-half);
|
gap: var(--Spacing-x-one-and-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
.innerContent .guests {
|
.guests {
|
||||||
color: var(--Text-Accent-Secondary);
|
color: var(--Text-Accent-Secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bedOptions {
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.imageContainer {
|
.imageContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: var(--Corner-radius-md);
|
border-radius: var(--Corner-radius-md);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bedOptions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listItem {
|
|
||||||
display: grid;
|
|
||||||
grid-auto-flow: column;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
margin-bottom: var(--Spacing-x-half);
|
|
||||||
align-items: self-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
color: var(--Text-Secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonContainer {
|
.buttonContainer {
|
||||||
background-color: var(--Base-Background-Primary-Normal);
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
border-top: 1px solid var(--Base-Border-Subtle);
|
border-top: 1px solid var(--Base-Border-Subtle);
|
||||||
|
|||||||
Reference in New Issue
Block a user