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 { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { selectRateWithParams } from "@/constants/routes/hotelReservation"
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import ImageGallery from "@/components/ImageGallery"
|
||||
import { getBedIconName } from "@/components/SidePeeks/RoomSidePeek/bedIcon"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||
import { getIntl } from "@/i18n"
|
||||
@@ -16,7 +14,7 @@ import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
||||
|
||||
import { getRoomNameAsParam } from "../../utils"
|
||||
import RoomFacilities from "./RoomFacilities"
|
||||
import { getBedDescriptionText } from "./utils"
|
||||
import RoomTypes from "./RoomTypes"
|
||||
|
||||
import styles from "./room.module.css"
|
||||
|
||||
@@ -28,7 +26,15 @@ export default async function RoomSidePeek({
|
||||
}: RoomSidePeekProps) {
|
||||
const lang = await getLang()
|
||||
const intl = await getIntl()
|
||||
const { roomSize, totalOccupancy, descriptions, images } = room
|
||||
const {
|
||||
roomSize,
|
||||
roomTypes,
|
||||
roomFacilities,
|
||||
name,
|
||||
totalOccupancy,
|
||||
descriptions,
|
||||
images,
|
||||
} = room
|
||||
const roomDescription = descriptions.medium
|
||||
|
||||
const galleryImages = mapApiImagesToGalleryImages(images)
|
||||
@@ -36,18 +42,13 @@ export default async function RoomSidePeek({
|
||||
const fromdate = dt().format("YYYY-MM-DD")
|
||||
const todate = dt().add(1, "day").format("YYYY-MM-DD")
|
||||
const selectRateURL = selectRateWithParams(lang, hotelId, fromdate, todate)
|
||||
|
||||
return (
|
||||
<SidePeek
|
||||
contentKey={`room-${getRoomNameAsParam(room.name)}`}
|
||||
title={room.name}
|
||||
>
|
||||
<SidePeek contentKey={`room-${getRoomNameAsParam(name)}`} title={name}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.innerContent}>
|
||||
<Typography
|
||||
variant="Body/Paragraph/mdRegular"
|
||||
className={styles.guests}
|
||||
>
|
||||
<p>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.guests}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage:
|
||||
@@ -83,7 +84,7 @@ export default async function RoomSidePeek({
|
||||
<div className={styles.imageContainer}>
|
||||
<ImageGallery
|
||||
images={galleryImages}
|
||||
title={room.name}
|
||||
title={name}
|
||||
height={280}
|
||||
hideLabel={true}
|
||||
/>
|
||||
@@ -94,48 +95,11 @@ export default async function RoomSidePeek({
|
||||
</div>
|
||||
|
||||
<div className={styles.innerContent}>
|
||||
<RoomFacilities roomFacilities={room.roomFacilities} />
|
||||
<RoomFacilities roomFacilities={roomFacilities} />
|
||||
</div>
|
||||
|
||||
<div className={styles.innerContent}>
|
||||
<div className={styles.bedOptions}>
|
||||
<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>
|
||||
<RoomTypes roomTypes={roomTypes} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
|
||||
@@ -12,39 +12,16 @@
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.innerContent .guests {
|
||||
.guests {
|
||||
color: var(--Text-Accent-Secondary);
|
||||
}
|
||||
|
||||
.bedOptions {
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
position: relative;
|
||||
border-radius: var(--Corner-radius-md);
|
||||
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 {
|
||||
background-color: var(--Base-Background-Primary-Normal);
|
||||
border-top: 1px solid var(--Base-Border-Subtle);
|
||||
|
||||
Reference in New Issue
Block a user