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:
Hrishikesh Vaipurkar
2025-05-21 12:27:11 +00:00
parent 16be305ad3
commit b3d5326adb
7 changed files with 95 additions and 96 deletions

View File

@@ -7,32 +7,9 @@ import type {
AlternativeHotelsAvailabilityInput,
AvailabilityInput,
} from "@/types/components/hotelReservation/selectHotel/availabilityInput"
import type {
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 { NullableHotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
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(
input: AvailabilityInput
): Promise<NullableHotelData[]> {
@@ -98,38 +75,3 @@ async function enhanceHotels(hotels: {
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: [] }
)
}

View File

@@ -33,3 +33,12 @@
forced-color-adjust: none;
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);
}

View File

@@ -13,6 +13,7 @@ export default function FilterCheckbox({
isSelected,
name,
id,
isDisabled,
onChange,
}: FilterCheckboxProps) {
return (
@@ -20,6 +21,7 @@ export default function FilterCheckbox({
className={styles.container}
isSelected={isSelected}
onChange={() => onChange(id)}
isDisabled={isDisabled}
>
{({ isSelected }) => (
<>

View File

@@ -14,7 +14,10 @@ import FilterCheckbox from "./FilterCheckbox"
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) {
const intl = useIntl()
@@ -49,6 +52,47 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
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 (
<aside className={`${styles.container} ${className}`}>
<form>
@@ -63,20 +107,7 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
defaultMessage: "Hotel facilities",
})}
</Subtitle>
<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>
<ul>{filterOutput(filters.facilityFilters)}</ul>
</div>
<div className={styles.facilities}>
@@ -85,20 +116,7 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
defaultMessage: "Hotel surroundings",
})}
</Subtitle>
<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>
<ul>{filterOutput(filters.surroundingsFilters)}</ul>
</div>
</form>
</aside>

View File

@@ -9,13 +9,16 @@ import type {
AlternativeHotelsAvailabilityInput,
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 type {
AlternativeHotelsSearchParams,
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 {
HotelLocation,
@@ -253,12 +256,31 @@ export function getFiltersFromHotels(
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 filterList: DetailedFacility[] = uniqueFilterIds
.map((filterId) => filters.find((filter) => filter.id === filterId))
.filter((filter): filter is DetailedFacility => filter !== undefined)
const filterList: HotelFilter[] = uniqueFilterIds
.map((filterId) => {
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)
return filterList.reduce<CategorizedFilters>((filters, filter) => {

View File

@@ -1,6 +1,7 @@
export type FilterCheckboxProps = {
name: string
id: string
isDisabled?: boolean
isSelected: boolean
onChange: (filterId: string) => void
}

View File

@@ -1,8 +1,13 @@
import type { Hotel } from "@/types/hotel"
export type HotelFilter = Hotel["detailedFacilities"][number] & {
hotelId: string
hotelIds: string[]
}
export type CategorizedFilters = {
facilityFilters: Hotel["detailedFacilities"]
surroundingsFilters: Hotel["detailedFacilities"]
facilityFilters: HotelFilter[]
surroundingsFilters: HotelFilter[]
}
export type HotelFiltersProps = {
filters: CategorizedFilters