feat/sw-251 Filter hotels on select hotel page
This commit is contained in:
@@ -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[] {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} />
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user