feat/sw-251 Filter hotels on select hotel page

This commit is contained in:
Niclas Edenvin
2024-11-06 15:22:02 +01:00
parent 560fb25aee
commit a8558eb499
5 changed files with 124 additions and 20 deletions

View File

@@ -6,7 +6,10 @@ import { getLang } from "@/i18n/serverContext"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import type { AvailabilityInput } from "@/types/components/hotelReservation/selectHotel/availabilityInput"
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { Filter } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
import type {
CategorizedFilters,
Filter,
} from "@/types/components/hotelReservation/selectHotel/hotelFilters"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import {
type PointOfInterest,
@@ -14,6 +17,15 @@ import {
PointOfInterestGroupEnum,
} from "@/types/hotel"
const hotelSurroundingsFilterNames = [
"Hotel surroundings",
"Hotel omgivelser",
"Hotelumgebung",
"Hotellia lähellä",
"Hotellomgivelser",
"Omgivningar",
]
export async function fetchAvailableHotels(
input: AvailabilityInput
): Promise<HotelData[]> {
@@ -39,7 +51,7 @@ export async function fetchAvailableHotels(
return await Promise.all(hotels)
}
export function getFiltersFromHotels(hotels: HotelData[]) {
export function getFiltersFromHotels(hotels: HotelData[]): CategorizedFilters {
const filters = hotels.flatMap((hotel) => hotel.hotelData.detailedFacilities)
const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))]
@@ -47,7 +59,21 @@ export function getFiltersFromHotels(hotels: HotelData[]) {
.map((filterId) => filters.find((filter) => filter.id === filterId))
.filter((filter): filter is Filter => filter !== undefined)
return filterList
return filterList.reduce<CategorizedFilters>(
(acc, filter) => {
if (filter.filter && hotelSurroundingsFilterNames.includes(filter.filter))
return {
facilityFilters: acc.facilityFilters,
surroundingsFilters: [...acc.surroundingsFilters, filter],
}
return {
facilityFilters: [...acc.facilityFilters, filter],
surroundingsFilters: acc.surroundingsFilters,
}
},
{ facilityFilters: [], surroundingsFilters: [] }
)
}
export function getPointOfInterests(hotels: HotelData[]): PointOfInterest[] {

View File

@@ -1,3 +1,6 @@
"use client"
import { useIntl } from "react-intl"
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
import { PriceTagIcon, ScandicLogoIcon } from "@/components/Icons"
import TripAdvisorIcon from "@/components/Icons/TripAdvisor"
@@ -17,8 +20,8 @@ import styles from "./hotelCard.module.css"
import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps"
export default async function HotelCard({ hotel }: HotelCardProps) {
const intl = await getIntl()
export default function HotelCard({ hotel }: HotelCardProps) {
const intl = useIntl()
const { hotelData } = hotel
const { price } = hotel

View File

@@ -1,3 +1,7 @@
"use client"
import { useSearchParams } from "next/navigation"
import { useMemo } from "react"
import Title from "@/components/TempDesignSystem/Text/Title"
import HotelCard from "../HotelCard"
@@ -7,12 +11,25 @@ import styles from "./hotelCardListing.module.css"
import { HotelCardListingProps } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
export default function HotelCardListing({ hotelData }: HotelCardListingProps) {
// TODO: filter with url params
const searchParams = useSearchParams()
const hotels = useMemo(() => {
const appliedFilters = searchParams.get("filters")?.split(",")
if (!appliedFilters || appliedFilters.length === 0) return hotelData
return hotelData.filter((hotel) =>
appliedFilters.every((appliedFilterId) =>
hotel.hotelData.detailedFacilities.some(
(facility) => facility.id.toString() === appliedFilterId
)
)
)
}, [searchParams, hotelData])
return (
<section className={styles.hotelCards}>
{hotelData && hotelData.length ? (
hotelData.map((hotel) => (
{hotels.length ? (
hotels.map((hotel) => (
<HotelCard key={hotel.hotelData.name} hotel={hotel} />
))
) : (

View File

@@ -1,5 +1,8 @@
"use client"
import { usePathname, useSearchParams } from "next/navigation"
import { useCallback, useEffect } from "react"
import { useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import styles from "./hotelFilter.module.css"
@@ -8,26 +11,77 @@ import { HotelFiltersProps } from "@/types/components/hotelReservation/selectHot
export default function HotelFilter({ filters }: HotelFiltersProps) {
const intl = useIntl()
const searchParams = useSearchParams()
const pathname = usePathname()
function handleOnChange() {
// TODO: Update URL with selected values
}
const { watch, handleSubmit, getValues, register } = useForm<
Record<string, boolean | undefined>
>({
defaultValues: searchParams
?.get("filters")
?.split(",")
.reduce((acc, curr) => ({ ...acc, [curr]: true }), {}),
})
const submitFilter = useCallback(() => {
const newSearchParams = new URLSearchParams(searchParams)
const values = Object.entries(getValues())
.filter(([_, value]) => !!value)
.map(([key, _]) => key)
.join(",")
if (values === "") {
newSearchParams.delete("filters")
} else {
newSearchParams.set("filters", values)
}
if (values !== searchParams.values.toString()) {
window.history.replaceState(
null,
"",
`${pathname}?${newSearchParams.toString()}`
)
}
}, [getValues, pathname, searchParams])
useEffect(() => {
const subscription = watch(() => handleSubmit(submitFilter)())
return () => subscription.unsubscribe()
}, [handleSubmit, watch, submitFilter])
return (
<aside className={styles.container}>
<div className={styles.facilities}>
{intl.formatMessage({ id: "Hotel facilities" })}
<form>
<form onSubmit={handleSubmit(submitFilter)}>
{intl.formatMessage({ id: "Hotel facilities" })}
<ul>
{filters.map((data) => (
<li key={data.id} className={styles.filter}>
{filters.facilityFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<input
id={`${data.id}`}
name={data.name}
type="checkbox"
onChange={handleOnChange}
id={`checkbox-${filter.id.toString()}`}
{...register(filter.id.toString())}
/>
<label htmlFor={`${data?.id}`}>{data?.name}</label>
<label htmlFor={`checkbox-${filter.id.toString()}`}>
{filter.name}
</label>
</li>
))}
</ul>
{intl.formatMessage({ id: "Hotel surroundings" })}
<ul>
{filters.surroundingsFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<input
type="checkbox"
id={`checkbox-${filter.id.toString()}`}
{...register(filter.id.toString())}
/>
<label htmlFor={`checkbox-${filter.id.toString()}`}>
{filter.name}
</label>
</li>
))}
</ul>

View File

@@ -1,7 +1,11 @@
import { Hotel } from "@/types/hotel"
export type CategorizedFilters = {
facilityFilters: Hotel["detailedFacilities"]
surroundingsFilters: Hotel["detailedFacilities"]
}
export type HotelFiltersProps = {
filters: Hotel["detailedFacilities"]
filters: CategorizedFilters
}
export type Filter = {