Merged in feat/sw-620-sort-hotels (pull request #868)

Feat/sw-620 sort hotels

* SW-620 Add radio button to select box

* feat(SW-620): Implement sorting on select hotel

* Fix casing

* Shallow copy hoteldata

* Use translations

* Remove unnecessary style

* Import order

* Type


Approved-by: Pontus Dreij
This commit is contained in:
Niclas Edenvin
2024-11-11 08:55:49 +00:00
parent 8fbfb00ead
commit 0f97757e31
14 changed files with 179 additions and 33 deletions

View File

@@ -9,6 +9,15 @@
margin: 0 auto;
}
.header {
display: flex;
margin: 0 auto;
padding: var(--Spacing-x4) var(--Spacing-x5) var(--Spacing-x3)
var(--Spacing-x5);
justify-content: space-between;
max-width: var(--max-width);
}
.sideBar {
display: flex;
flex-direction: column;

View File

@@ -9,6 +9,7 @@ import {
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
import HotelCardListing from "@/components/HotelReservation/HotelCardListing"
import HotelFilter from "@/components/HotelReservation/SelectHotel/HotelFilter"
import HotelSorter from "@/components/HotelReservation/SelectHotel/HotelSorter"
import MobileMapButtonContainer from "@/components/HotelReservation/SelectHotel/MobileMapButtonContainer"
import {
generateChildrenString,
@@ -61,33 +62,39 @@ export default async function SelectHotelPage({
const filterList = getFiltersFromHotels(hotels)
return (
<main className={styles.main}>
<div className={styles.sideBar}>
<Link
className={styles.link}
color="burgundy"
href={selectHotelMap[params.lang]}
keepSearchParams
>
<div className={styles.mapContainer}>
<StaticMap
city={searchParams.city}
width={340}
height={180}
zoomLevel={11}
mapType="roadmap"
altText={`Map of ${searchParams.city} city center`}
/>
<div className={styles.mapLinkText}>
{intl.formatMessage({ id: "Show map" })}
<ChevronRightIcon color="burgundy" width={20} height={20} />
<>
<header className={styles.header}>
<div>{city.name}</div>
<HotelSorter />
</header>
<main className={styles.main}>
<div className={styles.sideBar}>
<Link
className={styles.link}
color="burgundy"
href={selectHotelMap[params.lang]}
keepSearchParams
>
<div className={styles.mapContainer}>
<StaticMap
city={searchParams.city}
width={340}
height={180}
zoomLevel={11}
mapType="roadmap"
altText={`Map of ${searchParams.city} city center`}
/>
<div className={styles.mapLinkText}>
{intl.formatMessage({ id: "Show map" })}
<ChevronRightIcon color="burgundy" width={20} height={20} />
</div>
</div>
</div>
</Link>
<MobileMapButtonContainer city={searchParams.city} />
<HotelFilter filters={filterList} />
</div>
<HotelCardListing hotelData={hotels} />
</main>
</Link>
<MobileMapButtonContainer city={searchParams.city} />
<HotelFilter filters={filterList} />
</div>
<HotelCardListing hotelData={hotels} />
</main>
</>
)
}

View File

@@ -5,6 +5,7 @@ import { useMemo } from "react"
import Title from "@/components/TempDesignSystem/Text/Title"
import HotelCard from "../HotelCard"
import { DEFAULT_SORT } from "../SelectHotel/HotelSorter"
import styles from "./hotelCardListing.module.css"
@@ -12,6 +13,7 @@ import {
type HotelCardListingProps,
HotelCardListingTypeEnum,
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
export default function HotelCardListing({
hotelData,
@@ -21,25 +23,58 @@ export default function HotelCardListing({
}: HotelCardListingProps) {
const searchParams = useSearchParams()
const sortBy = useMemo(
() => searchParams.get("sort") ?? DEFAULT_SORT,
[searchParams]
)
const sortedHotels = useMemo(() => {
switch (sortBy) {
case SortOrder.Name:
return [...hotelData].sort((a, b) =>
a.hotelData.name.localeCompare(b.hotelData.name)
)
case SortOrder.TripAdvisorRating:
return [...hotelData].sort(
(a, b) =>
(b.hotelData.ratings?.tripAdvisor.rating ?? 0) -
(a.hotelData.ratings?.tripAdvisor.rating ?? 0)
)
case SortOrder.Price:
return [...hotelData].sort(
(a, b) =>
parseInt(a.price?.memberAmount ?? "0", 10) -
parseInt(b.price?.memberAmount ?? "0", 10)
)
case SortOrder.Distance:
default:
return [...hotelData].sort(
(a, b) =>
a.hotelData.location.distanceToCentre -
b.hotelData.location.distanceToCentre
)
}
}, [hotelData, sortBy])
const hotels = useMemo(() => {
const appliedFilters = searchParams.get("filters")?.split(",")
if (!appliedFilters || appliedFilters.length === 0) return hotelData
if (!appliedFilters || appliedFilters.length === 0) return sortedHotels
return hotelData.filter((hotel) =>
return sortedHotels.filter((hotel) =>
appliedFilters.every((appliedFilterId) =>
hotel.hotelData.detailedFacilities.some(
(facility) => facility.id.toString() === appliedFilterId
)
)
)
}, [searchParams, hotelData])
}, [searchParams, sortedHotels])
return (
<section className={styles.hotelCards}>
{hotels?.length ? (
hotels.map((hotel) => (
<HotelCard
key={hotel.hotelData.name}
key={hotel.hotelData.operaId}
hotel={hotel}
type={type}
state={hotel.hotelData.name === activeCard ? "active" : "default"}

View File

@@ -0,0 +1,56 @@
"use client"
import { usePathname, useSearchParams } from "next/navigation"
import { useCallback } from "react"
import { useIntl } from "react-intl"
import Select from "@/components/TempDesignSystem/Select"
import {
type SortItem,
SortOrder,
} from "@/types/components/hotelReservation/selectHotel/hotelSorter"
const sortItems: SortItem[] = [
{ label: "Distance", value: SortOrder.Distance },
{ label: "Name", value: SortOrder.Name },
{ label: "Price", value: SortOrder.Price },
{ label: "TripAdvisor rating", value: SortOrder.TripAdvisorRating },
]
export const DEFAULT_SORT = SortOrder.Distance
export default function HotelSorter() {
const searchParams = useSearchParams()
const pathname = usePathname()
const i18n = useIntl()
const onSelect = useCallback(
(value: string | number) => {
const newSort = value.toString()
if (newSort === searchParams.get("sort")) {
return
}
const newSearchParams = new URLSearchParams(searchParams)
newSearchParams.set("sort", newSort)
window.history.replaceState(
null,
"",
`${pathname}?${newSearchParams.toString()}`
)
},
[pathname, searchParams]
)
return (
<Select
items={sortItems}
label={i18n.formatMessage({ id: "Sort by" })}
name="sort"
showRadioButton
onSelect={onSelect}
/>
)
}

View File

@@ -35,6 +35,7 @@ export default function Select({
tabIndex,
value,
maxHeight,
showRadioButton = false,
}: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined)
@@ -88,7 +89,7 @@ export default function Select({
{items.map((item) => (
<ListBoxItem
aria-label={String(item)}
className={styles.listBoxItem}
className={`${styles.listBoxItem} ${showRadioButton && styles.showRadioButton}`}
id={item.value}
key={item.label}
data-testid={item.label}

View File

@@ -52,11 +52,11 @@
display: flex;
flex-direction: column;
gap: var(--Spacing-x1);
padding: var(--Spacing-x2);
}
.listBoxItem {
padding: var(--Spacing-x1);
margin: var(--Spacing-x0) var(--Spacing-x2);
}
.listBoxItem[data-focused="true"],
@@ -65,3 +65,23 @@
border-radius: var(--Corner-radius-Medium,);
outline: none;
}
.listBoxItem.showRadioButton {
display: flex;
align-items: center;
}
.listBoxItem.showRadioButton:before {
display: flex;
content: "";
margin-right: var(--Spacing-x-one-and-half);
background-color: white;
width: 24px;
height: 24px;
border-radius: 50%;
box-shadow: inset 0 0 0 2px var(--Base-Border-Normal);
}
.listBoxItem[data-selected="true"].showRadioButton:before {
box-shadow: inset 0 0 0 8px var(--UI-Input-Controls-Fill-Selected);
}

View File

@@ -10,6 +10,7 @@ export interface SelectProps
placeholder?: string
value?: string | number
maxHeight?: number
showRadioButton?: boolean
}
export type SelectPortalContainer = HTMLDivElement | undefined

View File

@@ -313,6 +313,7 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.",
"Something went wrong!": "Noget gik galt!",
"Sort by": "Sorter efter",
"Sports": "Sport",
"Standard price": "Standardpris",
"Street": "Gade",

View File

@@ -312,6 +312,7 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.",
"Something went wrong!": "Etwas ist schief gelaufen!",
"Sort by": "Sortieren nach",
"Sports": "Sport",
"Standard price": "Standardpreis",
"Street": "Straße",

View File

@@ -343,6 +343,7 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.",
"Something went wrong!": "Something went wrong!",
"Sort by": "Sort by",
"Sports": "Sports",
"Standard price": "Standard price",
"Street": "Street",

View File

@@ -314,6 +314,7 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.",
"Something went wrong!": "Jotain meni pieleen!",
"Sort by": "Lajitteluperuste",
"Sports": "Urheilu",
"Standard price": "Normaali hinta",
"Street": "Katu",

View File

@@ -311,6 +311,7 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.",
"Something went wrong!": "Noe gikk galt!",
"Sort by": "Sorter etter",
"Sports": "Sport",
"Standard price": "Standardpris",
"Street": "Gate",

View File

@@ -311,6 +311,7 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.",
"Something went wrong!": "Något gick fel!",
"Sort by": "Sortera efter",
"Sports": "Sport",
"Standard price": "Standardpris",
"Street": "Gata",

View File

@@ -0,0 +1,11 @@
export const enum SortOrder {
Distance = "distance",
Name = "name",
Price = "price",
TripAdvisorRating = "tripadvisor",
}
export type SortItem = {
label: string
value: string
}