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; 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 { .sideBar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

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

View File

@@ -5,6 +5,7 @@ import { useMemo } from "react"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import HotelCard from "../HotelCard" import HotelCard from "../HotelCard"
import { DEFAULT_SORT } from "../SelectHotel/HotelSorter"
import styles from "./hotelCardListing.module.css" import styles from "./hotelCardListing.module.css"
@@ -12,6 +13,7 @@ import {
type HotelCardListingProps, type HotelCardListingProps,
HotelCardListingTypeEnum, HotelCardListingTypeEnum,
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
export default function HotelCardListing({ export default function HotelCardListing({
hotelData, hotelData,
@@ -21,25 +23,58 @@ export default function HotelCardListing({
}: HotelCardListingProps) { }: HotelCardListingProps) {
const searchParams = useSearchParams() 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 hotels = useMemo(() => {
const appliedFilters = searchParams.get("filters")?.split(",") 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) => appliedFilters.every((appliedFilterId) =>
hotel.hotelData.detailedFacilities.some( hotel.hotelData.detailedFacilities.some(
(facility) => facility.id.toString() === appliedFilterId (facility) => facility.id.toString() === appliedFilterId
) )
) )
) )
}, [searchParams, hotelData]) }, [searchParams, sortedHotels])
return ( return (
<section className={styles.hotelCards}> <section className={styles.hotelCards}>
{hotels?.length ? ( {hotels?.length ? (
hotels.map((hotel) => ( hotels.map((hotel) => (
<HotelCard <HotelCard
key={hotel.hotelData.name} key={hotel.hotelData.operaId}
hotel={hotel} hotel={hotel}
type={type} type={type}
state={hotel.hotelData.name === activeCard ? "active" : "default"} 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, tabIndex,
value, value,
maxHeight, maxHeight,
showRadioButton = false,
}: SelectProps) { }: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined) const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined)
@@ -88,7 +89,7 @@ export default function Select({
{items.map((item) => ( {items.map((item) => (
<ListBoxItem <ListBoxItem
aria-label={String(item)} aria-label={String(item)}
className={styles.listBoxItem} className={`${styles.listBoxItem} ${showRadioButton && styles.showRadioButton}`}
id={item.value} id={item.value}
key={item.label} key={item.label}
data-testid={item.label} data-testid={item.label}

View File

@@ -52,11 +52,11 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--Spacing-x1); gap: var(--Spacing-x1);
padding: var(--Spacing-x2);
} }
.listBoxItem { .listBoxItem {
padding: var(--Spacing-x1); padding: var(--Spacing-x1);
margin: var(--Spacing-x0) var(--Spacing-x2);
} }
.listBoxItem[data-focused="true"], .listBoxItem[data-focused="true"],
@@ -65,3 +65,23 @@
border-radius: var(--Corner-radius-Medium,); border-radius: var(--Corner-radius-Medium,);
outline: none; 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 placeholder?: string
value?: string | number value?: string | number
maxHeight?: number maxHeight?: number
showRadioButton?: boolean
} }
export type SelectPortalContainer = HTMLDivElement | undefined 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 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 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!", "Something went wrong!": "Noget gik galt!",
"Sort by": "Sorter efter",
"Sports": "Sport", "Sports": "Sport",
"Standard price": "Standardpris", "Standard price": "Standardpris",
"Street": "Gade", "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 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 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!", "Something went wrong!": "Etwas ist schief gelaufen!",
"Sort by": "Sortieren nach",
"Sports": "Sport", "Sports": "Sport",
"Standard price": "Standardpreis", "Standard price": "Standardpreis",
"Street": "Straße", "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 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 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!", "Something went wrong!": "Something went wrong!",
"Sort by": "Sort by",
"Sports": "Sports", "Sports": "Sports",
"Standard price": "Standard price", "Standard price": "Standard price",
"Street": "Street", "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 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 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!", "Something went wrong!": "Jotain meni pieleen!",
"Sort by": "Lajitteluperuste",
"Sports": "Urheilu", "Sports": "Urheilu",
"Standard price": "Normaali hinta", "Standard price": "Normaali hinta",
"Street": "Katu", "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 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 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!", "Something went wrong!": "Noe gikk galt!",
"Sort by": "Sorter etter",
"Sports": "Sport", "Sports": "Sport",
"Standard price": "Standardpris", "Standard price": "Standardpris",
"Street": "Gate", "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 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 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!", "Something went wrong!": "Något gick fel!",
"Sort by": "Sortera efter",
"Sports": "Sport", "Sports": "Sport",
"Standard price": "Standardpris", "Standard price": "Standardpris",
"Street": "Gata", "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
}