Merged in feat/SW-1232-filter-improvements-select-hot (pull request #2168)
feat: SW-1232 Implemented disabling of filters and show hotel count * feat: SW-1232 Implemented disabling of filters and show hotel count * feat: SW-1232 Optimised code Approved-by: Niclas Edenvin
This commit is contained in:
@@ -7,32 +7,9 @@ import type {
|
|||||||
AlternativeHotelsAvailabilityInput,
|
AlternativeHotelsAvailabilityInput,
|
||||||
AvailabilityInput,
|
AvailabilityInput,
|
||||||
} from "@/types/components/hotelReservation/selectHotel/availabilityInput"
|
} from "@/types/components/hotelReservation/selectHotel/availabilityInput"
|
||||||
import type {
|
import type { NullableHotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||||
HotelData,
|
|
||||||
NullableHotelData,
|
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
|
||||||
import type { CategorizedFilters } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
|
||||||
import type { DetailedFacility } from "@/types/hotel"
|
|
||||||
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
||||||
|
|
||||||
const hotelSurroundingsFilterNames = [
|
|
||||||
"Hotel surroundings",
|
|
||||||
"Hotel omgivelser",
|
|
||||||
"Hotelumgebung",
|
|
||||||
"Hotellia lähellä",
|
|
||||||
"Hotellomgivelser",
|
|
||||||
"Omgivningar",
|
|
||||||
]
|
|
||||||
|
|
||||||
const hotelFacilitiesFilterNames = [
|
|
||||||
"Hotel facilities",
|
|
||||||
"Hotellfaciliteter",
|
|
||||||
"Hotelfaciliteter",
|
|
||||||
"Hotel faciliteter",
|
|
||||||
"Hotel-Infos",
|
|
||||||
"Hotellin palvelut",
|
|
||||||
]
|
|
||||||
|
|
||||||
export async function fetchAvailableHotels(
|
export async function fetchAvailableHotels(
|
||||||
input: AvailabilityInput
|
input: AvailabilityInput
|
||||||
): Promise<NullableHotelData[]> {
|
): Promise<NullableHotelData[]> {
|
||||||
@@ -98,38 +75,3 @@ async function enhanceHotels(hotels: {
|
|||||||
|
|
||||||
return await Promise.all(hotelFetchers)
|
return await Promise.all(hotelFetchers)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFiltersFromHotels(hotels: HotelData[]): CategorizedFilters {
|
|
||||||
if (hotels.length === 0)
|
|
||||||
return { facilityFilters: [], surroundingsFilters: [] }
|
|
||||||
|
|
||||||
const filters = hotels.flatMap((hotel) => {
|
|
||||||
if (!hotel.hotelData) return []
|
|
||||||
return hotel.hotelData.detailedFacilities
|
|
||||||
})
|
|
||||||
|
|
||||||
const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))]
|
|
||||||
const filterList: DetailedFacility[] = uniqueFilterIds
|
|
||||||
.map((filterId) => filters.find((filter) => filter.id === filterId))
|
|
||||||
.filter((filter): filter is DetailedFacility => filter !== undefined)
|
|
||||||
.sort((a, b) => b.sortOrder - a.sortOrder)
|
|
||||||
|
|
||||||
return filterList.reduce<CategorizedFilters>(
|
|
||||||
(acc, filter) => {
|
|
||||||
if (filter.filter && hotelSurroundingsFilterNames.includes(filter.filter))
|
|
||||||
return {
|
|
||||||
facilityFilters: acc.facilityFilters,
|
|
||||||
surroundingsFilters: [...acc.surroundingsFilters, filter],
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.filter && hotelFacilitiesFilterNames.includes(filter.filter))
|
|
||||||
return {
|
|
||||||
facilityFilters: [...acc.facilityFilters, filter],
|
|
||||||
surroundingsFilters: acc.surroundingsFilters,
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc
|
|
||||||
},
|
|
||||||
{ facilityFilters: [], surroundingsFilters: [] }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -33,3 +33,12 @@
|
|||||||
forced-color-adjust: none;
|
forced-color-adjust: none;
|
||||||
background: var(--UI-Input-Controls-Surface-Normal);
|
background: var(--UI-Input-Controls-Surface-Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container[data-disabled] {
|
||||||
|
color: var(--Text-Interactive-Disabled);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.container[data-disabled] .checkbox {
|
||||||
|
border-color: var(--Text-Interactive-Disabled);
|
||||||
|
background: var(--Surface-Primary-Disabled);
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default function FilterCheckbox({
|
|||||||
isSelected,
|
isSelected,
|
||||||
name,
|
name,
|
||||||
id,
|
id,
|
||||||
|
isDisabled,
|
||||||
onChange,
|
onChange,
|
||||||
}: FilterCheckboxProps) {
|
}: FilterCheckboxProps) {
|
||||||
return (
|
return (
|
||||||
@@ -20,6 +21,7 @@ export default function FilterCheckbox({
|
|||||||
className={styles.container}
|
className={styles.container}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onChange={() => onChange(id)}
|
onChange={() => onChange(id)}
|
||||||
|
isDisabled={isDisabled}
|
||||||
>
|
>
|
||||||
{({ isSelected }) => (
|
{({ isSelected }) => (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import FilterCheckbox from "./FilterCheckbox"
|
|||||||
|
|
||||||
import styles from "./hotelFilter.module.css"
|
import styles from "./hotelFilter.module.css"
|
||||||
|
|
||||||
import type { HotelFiltersProps } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
import type {
|
||||||
|
HotelFilter,
|
||||||
|
HotelFiltersProps,
|
||||||
|
} from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
||||||
|
|
||||||
export default function HotelFilter({ className, filters }: HotelFiltersProps) {
|
export default function HotelFilter({ className, filters }: HotelFiltersProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
@@ -49,6 +52,47 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filteredHotelIds: string[] | undefined
|
||||||
|
if (activeFilters.length) {
|
||||||
|
const allFilters = [
|
||||||
|
...filters.facilityFilters,
|
||||||
|
...filters.surroundingsFilters,
|
||||||
|
]
|
||||||
|
filteredHotelIds = allFilters
|
||||||
|
.filter((f) => activeFilters.includes(f.id.toString()))
|
||||||
|
.map((f) => f.hotelIds)
|
||||||
|
.reduce((accumlatedHotelIds, currentHotelIds) =>
|
||||||
|
accumlatedHotelIds?.filter((hotelId) =>
|
||||||
|
currentHotelIds?.includes(hotelId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterOutput(filters: HotelFilter[]) {
|
||||||
|
return filters.map((filter) => {
|
||||||
|
const isDisabled = filteredHotelIds?.length
|
||||||
|
? !filter.hotelIds?.some((hotelId) =>
|
||||||
|
filteredHotelIds.includes(hotelId)
|
||||||
|
)
|
||||||
|
: false
|
||||||
|
return (
|
||||||
|
<li key={`li-${filter.id}`} className={styles.filter}>
|
||||||
|
<FilterCheckbox
|
||||||
|
name={filter.name}
|
||||||
|
id={filter.id.toString()}
|
||||||
|
onChange={() => toggleFilter(filter.id.toString())}
|
||||||
|
isSelected={activeFilters.some((f) => f === filter.id.toString())}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
{!isDisabled ? (
|
||||||
|
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
||||||
|
<span>{`(${filteredHotelIds?.filter((id) => filter.hotelIds.includes(id))?.length ?? filter.hotelIds.length})`}</span>
|
||||||
|
) : null}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={`${styles.container} ${className}`}>
|
<aside className={`${styles.container} ${className}`}>
|
||||||
<form>
|
<form>
|
||||||
@@ -63,20 +107,7 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
|
|||||||
defaultMessage: "Hotel facilities",
|
defaultMessage: "Hotel facilities",
|
||||||
})}
|
})}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<ul>
|
<ul>{filterOutput(filters.facilityFilters)}</ul>
|
||||||
{filters.facilityFilters.map((filter) => (
|
|
||||||
<li key={`li-${filter.id}`} className={styles.filter}>
|
|
||||||
<FilterCheckbox
|
|
||||||
name={filter.name}
|
|
||||||
id={filter.id.toString()}
|
|
||||||
onChange={() => toggleFilter(filter.id.toString())}
|
|
||||||
isSelected={
|
|
||||||
!!activeFilters.find((f) => f === filter.id.toString())
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.facilities}>
|
<div className={styles.facilities}>
|
||||||
@@ -85,20 +116,7 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
|
|||||||
defaultMessage: "Hotel surroundings",
|
defaultMessage: "Hotel surroundings",
|
||||||
})}
|
})}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<ul>
|
<ul>{filterOutput(filters.surroundingsFilters)}</ul>
|
||||||
{filters.surroundingsFilters.map((filter) => (
|
|
||||||
<li key={`li-${filter.id}`} className={styles.filter}>
|
|
||||||
<FilterCheckbox
|
|
||||||
name={filter.name}
|
|
||||||
id={filter.id.toString()}
|
|
||||||
onChange={() => toggleFilter(filter.id.toString())}
|
|
||||||
isSelected={
|
|
||||||
!!activeFilters.find((f) => f === filter.id.toString())
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ import type {
|
|||||||
AlternativeHotelsAvailabilityInput,
|
AlternativeHotelsAvailabilityInput,
|
||||||
AvailabilityInput,
|
AvailabilityInput,
|
||||||
} from "@/types/components/hotelReservation/selectHotel/availabilityInput"
|
} from "@/types/components/hotelReservation/selectHotel/availabilityInput"
|
||||||
import type { CategorizedFilters } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
import type {
|
||||||
|
CategorizedFilters,
|
||||||
|
HotelFilter,
|
||||||
|
} from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
||||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||||
import type {
|
import type {
|
||||||
AlternativeHotelsSearchParams,
|
AlternativeHotelsSearchParams,
|
||||||
SelectHotelSearchParams,
|
SelectHotelSearchParams,
|
||||||
} from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
} from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
||||||
import type { AdditionalData, DetailedFacility, Hotel } from "@/types/hotel"
|
import type { AdditionalData, Hotel } from "@/types/hotel"
|
||||||
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
||||||
import type {
|
import type {
|
||||||
HotelLocation,
|
HotelLocation,
|
||||||
@@ -253,12 +256,31 @@ export function getFiltersFromHotels(
|
|||||||
return defaultFilters
|
return defaultFilters
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = hotels.flatMap(({ hotel }) => hotel.detailedFacilities)
|
const filters = hotels.flatMap(({ hotel }) =>
|
||||||
|
hotel.detailedFacilities.map(
|
||||||
|
(facility) =>
|
||||||
|
<HotelFilter>{
|
||||||
|
...facility,
|
||||||
|
hotelId: hotel.operaId,
|
||||||
|
hotelIds: [hotel.operaId],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))]
|
const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))]
|
||||||
const filterList: DetailedFacility[] = uniqueFilterIds
|
const filterList: HotelFilter[] = uniqueFilterIds
|
||||||
.map((filterId) => filters.find((filter) => filter.id === filterId))
|
.map((filterId) => {
|
||||||
.filter((filter): filter is DetailedFacility => filter !== undefined)
|
const filter = filters.find((f) => f.id === filterId)
|
||||||
|
|
||||||
|
// List and include all hotel Ids having same filter / amenity
|
||||||
|
if (filter) {
|
||||||
|
filter.hotelIds = filters
|
||||||
|
.filter((f) => f.id === filterId)
|
||||||
|
.map((f) => f.hotelId)
|
||||||
|
}
|
||||||
|
return filter
|
||||||
|
})
|
||||||
|
.filter((filter): filter is HotelFilter => filter !== undefined)
|
||||||
.sort((a, b) => b.sortOrder - a.sortOrder)
|
.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||||
|
|
||||||
return filterList.reduce<CategorizedFilters>((filters, filter) => {
|
return filterList.reduce<CategorizedFilters>((filters, filter) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type FilterCheckboxProps = {
|
export type FilterCheckboxProps = {
|
||||||
name: string
|
name: string
|
||||||
id: string
|
id: string
|
||||||
|
isDisabled?: boolean
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
onChange: (filterId: string) => void
|
onChange: (filterId: string) => void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import type { Hotel } from "@/types/hotel"
|
import type { Hotel } from "@/types/hotel"
|
||||||
|
|
||||||
|
export type HotelFilter = Hotel["detailedFacilities"][number] & {
|
||||||
|
hotelId: string
|
||||||
|
hotelIds: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export type CategorizedFilters = {
|
export type CategorizedFilters = {
|
||||||
facilityFilters: Hotel["detailedFacilities"]
|
facilityFilters: HotelFilter[]
|
||||||
surroundingsFilters: Hotel["detailedFacilities"]
|
surroundingsFilters: HotelFilter[]
|
||||||
}
|
}
|
||||||
export type HotelFiltersProps = {
|
export type HotelFiltersProps = {
|
||||||
filters: CategorizedFilters
|
filters: CategorizedFilters
|
||||||
|
|||||||
Reference in New Issue
Block a user