Merged in fix/SW-1111-issues-with-map-select-hotel (pull request #1057)
Fix/SW-1111 issues with map select hotel Approved-by: Bianca Widstam
This commit is contained in:
@@ -4,7 +4,8 @@ import { memo, useCallback } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { selectHotelMap, selectRate } from "@/constants/routes/hotelReservation"
|
||||
import { selectRate } from "@/constants/routes/hotelReservation"
|
||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
||||
|
||||
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
|
||||
import ImageGallery from "@/components/ImageGallery"
|
||||
@@ -32,26 +33,27 @@ function HotelCard({
|
||||
hotel,
|
||||
type = HotelCardListingTypeEnum.PageListing,
|
||||
state = "default",
|
||||
onHotelCardHover,
|
||||
}: HotelCardProps) {
|
||||
const params = useParams()
|
||||
const lang = params.lang as Lang
|
||||
const intl = useIntl()
|
||||
const { setActiveHotelPin, setActiveHotelCard } = useHotelsMapStore()
|
||||
|
||||
const { hotelData } = hotel
|
||||
const { price } = hotel
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
if (onHotelCardHover && hotelData) {
|
||||
onHotelCardHover(hotelData.name)
|
||||
if (hotelData) {
|
||||
setActiveHotelPin(hotelData.name)
|
||||
}
|
||||
}, [onHotelCardHover, hotelData])
|
||||
}, [setActiveHotelPin, hotelData])
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
if (onHotelCardHover) {
|
||||
onHotelCardHover(null)
|
||||
if (hotelData) {
|
||||
setActiveHotelPin(null)
|
||||
setActiveHotelCard(null)
|
||||
}
|
||||
}, [onHotelCardHover])
|
||||
}, [setActiveHotelPin, hotelData, setActiveHotelCard])
|
||||
|
||||
if (!hotel || !hotelData) return null
|
||||
|
||||
@@ -96,15 +98,21 @@ function HotelCard({
|
||||
{hotelData.address.streetAddress}, {hotelData.address.city}
|
||||
</Caption>
|
||||
</address>
|
||||
<Link
|
||||
className={styles.addressMobile}
|
||||
href={`${selectHotelMap(lang)}?selectedHotel=${hotelData.name}`}
|
||||
keepSearchParams
|
||||
>
|
||||
<Caption color="baseTextMediumContrast" type="underline">
|
||||
<Caption color="baseTextMediumContrast" type="underline" asChild>
|
||||
<Link
|
||||
href={`https://www.google.com/maps/dir/?api=1&destination=${hotelData.location.latitude},${hotelData.location.longitude}`}
|
||||
className={styles.googleMaps}
|
||||
target="_blank"
|
||||
aria-label={intl.formatMessage({
|
||||
id: "Driving directions",
|
||||
})}
|
||||
title={intl.formatMessage({
|
||||
id: "Driving directions",
|
||||
})}
|
||||
>
|
||||
{hotelData.address.streetAddress}, {hotelData.address.city}
|
||||
</Caption>
|
||||
</Link>
|
||||
</Link>
|
||||
</Caption>
|
||||
<div>
|
||||
<Divider variant="vertical" color="subtle" />
|
||||
</div>
|
||||
|
||||
@@ -106,8 +106,7 @@
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.facilities,
|
||||
.memberPrice {
|
||||
.facilities {
|
||||
display: none;
|
||||
}
|
||||
.dialog {
|
||||
|
||||
@@ -103,12 +103,14 @@ export default function HotelCardDialog({
|
||||
<Caption type="bold">
|
||||
{intl.formatMessage({ id: "From" })}
|
||||
</Caption>
|
||||
<Subtitle type="two">
|
||||
{publicPrice} {currency}
|
||||
<Body asChild>
|
||||
<span>/{intl.formatMessage({ id: "night" })}</span>
|
||||
</Body>
|
||||
</Subtitle>
|
||||
{publicPrice && (
|
||||
<Subtitle type="two">
|
||||
{publicPrice} {currency}
|
||||
<Body asChild>
|
||||
<span>/{intl.formatMessage({ id: "night" })}</span>
|
||||
</Body>
|
||||
</Subtitle>
|
||||
)}
|
||||
{memberPrice && (
|
||||
<Subtitle
|
||||
type="two"
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { useCallback, useEffect, useRef } from "react"
|
||||
import { useMediaQuery } from "usehooks-ts"
|
||||
|
||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
||||
|
||||
import useClickOutside from "@/hooks/useClickOutside"
|
||||
|
||||
import HotelCardDialog from "../HotelCardDialog"
|
||||
@@ -14,18 +16,21 @@ import type { HotelCardDialogListingProps } from "@/types/components/hotelReserv
|
||||
|
||||
export default function HotelCardDialogListing({
|
||||
hotels,
|
||||
activeCard,
|
||||
onActiveCardChange,
|
||||
}: HotelCardDialogListingProps) {
|
||||
const hotelsPinData = hotels ? getHotelPins(hotels) : []
|
||||
const activeCardRef = useRef<HTMLDivElement | null>(null)
|
||||
const observerRef = useRef<IntersectionObserver | null>(null)
|
||||
const dialogRef = useRef<HTMLDivElement>(null)
|
||||
const isMobile = useMediaQuery("(max-width: 768px)")
|
||||
const { activeHotelCard, setActiveHotelCard, setActiveHotelPin } =
|
||||
useHotelsMapStore()
|
||||
|
||||
useClickOutside(dialogRef, !!activeCard && isMobile, () => {
|
||||
onActiveCardChange(null)
|
||||
})
|
||||
function handleClose() {
|
||||
setActiveHotelCard(null)
|
||||
setActiveHotelPin(null)
|
||||
}
|
||||
|
||||
useClickOutside(dialogRef, !!activeHotelCard && isMobile, handleClose)
|
||||
|
||||
const handleIntersection = useCallback(
|
||||
(entries: IntersectionObserverEntry[]) => {
|
||||
@@ -33,12 +38,12 @@ export default function HotelCardDialogListing({
|
||||
if (entry.isIntersecting) {
|
||||
const cardName = entry.target.getAttribute("data-name")
|
||||
if (cardName) {
|
||||
onActiveCardChange(cardName)
|
||||
setActiveHotelCard(cardName)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
[onActiveCardChange]
|
||||
[setActiveHotelCard]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -73,13 +78,13 @@ export default function HotelCardDialogListing({
|
||||
elements.forEach((el) => observerRef.current?.observe(el))
|
||||
}, 1000)
|
||||
}
|
||||
}, [activeCard])
|
||||
}, [activeHotelCard])
|
||||
|
||||
return (
|
||||
<div className={styles.hotelCardDialogListing} ref={dialogRef}>
|
||||
{!!hotelsPinData?.length &&
|
||||
hotelsPinData.map((data) => {
|
||||
const isActive = data.name === activeCard
|
||||
const isActive = data.name === activeHotelCard
|
||||
return (
|
||||
<div
|
||||
key={data.name}
|
||||
@@ -88,8 +93,8 @@ export default function HotelCardDialogListing({
|
||||
>
|
||||
<HotelCardDialog
|
||||
data={data}
|
||||
isOpen={!!activeCard}
|
||||
handleClose={() => onActiveCardChange(null)}
|
||||
isOpen={!!activeHotelCard}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -12,7 +12,10 @@ export function getHotelPins(hotels: HotelData[]): HotelPin[] {
|
||||
name: hotel.hotelData.name,
|
||||
publicPrice: hotel.price?.public?.localPrice.pricePerNight ?? null,
|
||||
memberPrice: hotel.price?.member?.localPrice.pricePerNight ?? null,
|
||||
currency: hotel.price?.public?.localPrice.currency || null,
|
||||
currency:
|
||||
hotel.price?.public?.localPrice.currency ||
|
||||
hotel.price?.member?.localPrice.currency ||
|
||||
null,
|
||||
images: [
|
||||
hotel.hotelData.hotelContent.images,
|
||||
...(hotel.hotelData.gallery?.heroImages ?? []),
|
||||
@@ -25,5 +28,8 @@ export function getHotelPins(hotels: HotelData[]): HotelPin[] {
|
||||
.slice(0, 3),
|
||||
ratings: hotel.hotelData.ratings?.tripAdvisor.rating ?? null,
|
||||
operaId: hotel.hotelData.operaId,
|
||||
facilityIds: hotel.hotelData.detailedFacilities.map(
|
||||
(facility) => facility.id
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
||||
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
||||
@@ -24,14 +25,13 @@ import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
export default function HotelCardListing({
|
||||
hotelData,
|
||||
type = HotelCardListingTypeEnum.PageListing,
|
||||
activeCard,
|
||||
onHotelCardHover,
|
||||
}: HotelCardListingProps) {
|
||||
const searchParams = useSearchParams()
|
||||
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
||||
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
|
||||
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
|
||||
const intl = useIntl()
|
||||
const { activeHotelCard } = useHotelsMapStore()
|
||||
|
||||
const sortBy = useMemo(
|
||||
() => searchParams.get("sort") ?? DEFAULT_SORT,
|
||||
@@ -111,13 +111,16 @@ export default function HotelCardListing({
|
||||
hotels.map((hotel) => (
|
||||
<div
|
||||
key={hotel.hotelData.operaId}
|
||||
data-active={hotel.hotelData.name === activeCard ? "true" : "false"}
|
||||
data-active={
|
||||
hotel.hotelData.name === activeHotelCard ? "true" : "false"
|
||||
}
|
||||
>
|
||||
<HotelCard
|
||||
hotel={hotel}
|
||||
type={type}
|
||||
state={hotel.hotelData.name === activeCard ? "active" : "default"}
|
||||
onHotelCardHover={onHotelCardHover}
|
||||
state={
|
||||
hotel.hotelData.name === activeHotelCard ? "active" : "default"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
@@ -128,7 +131,9 @@ export default function HotelCardListing({
|
||||
text={intl.formatMessage({ id: "filters.nohotel.text" })}
|
||||
/>
|
||||
) : null}
|
||||
{showBackToTop && <BackToTopButton onClick={scrollToTop} />}
|
||||
{showBackToTop && (
|
||||
<BackToTopButton position="right" onClick={scrollToTop} />
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
usePathname,
|
||||
useSearchParams,
|
||||
} from "next/dist/client/components/navigation"
|
||||
import { useCallback, useState } from "react"
|
||||
import {
|
||||
Dialog as AriaDialog,
|
||||
DialogTrigger,
|
||||
@@ -13,25 +18,69 @@ import { useHotelFilterStore } from "@/stores/hotel-filters"
|
||||
import { CloseLargeIcon, FilterIcon } from "@/components/Icons"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import Select from "@/components/TempDesignSystem/Select"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useInitializeFiltersFromUrl from "@/hooks/useInitializeFiltersFromUrl"
|
||||
|
||||
import HotelFilter from "../HotelFilter"
|
||||
import HotelSorter from "../HotelSorter"
|
||||
import { DEFAULT_SORT } from "../HotelSorter"
|
||||
|
||||
import styles from "./filterAndSortModal.module.css"
|
||||
|
||||
import type { FilterAndSortModalProps } from "@/types/components/hotelReservation/selectHotel/filterAndSortModal"
|
||||
import {
|
||||
type SortItem,
|
||||
SortOrder,
|
||||
} from "@/types/components/hotelReservation/selectHotel/hotelSorter"
|
||||
|
||||
export default function FilterAndSortModal({
|
||||
filters,
|
||||
}: FilterAndSortModalProps) {
|
||||
const intl = useIntl()
|
||||
useInitializeFiltersFromUrl()
|
||||
const searchParams = useSearchParams()
|
||||
const pathname = usePathname()
|
||||
const resultCount = useHotelFilterStore((state) => state.resultCount)
|
||||
const setFilters = useHotelFilterStore((state) => state.setFilters)
|
||||
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
||||
const [sort, setSort] = useState(searchParams.get("sort") ?? DEFAULT_SORT)
|
||||
|
||||
const sortItems: SortItem[] = [
|
||||
{
|
||||
label: intl.formatMessage({ id: "Distance to city centre" }),
|
||||
value: SortOrder.Distance,
|
||||
},
|
||||
{ label: intl.formatMessage({ id: "Name" }), value: SortOrder.Name },
|
||||
{ label: intl.formatMessage({ id: "Price" }), value: SortOrder.Price },
|
||||
{
|
||||
label: intl.formatMessage({ id: "TripAdvisor rating" }),
|
||||
value: SortOrder.TripAdvisorRating,
|
||||
},
|
||||
]
|
||||
|
||||
const handleSortSelect = useCallback((value: string | number) => {
|
||||
setSort(value.toString())
|
||||
}, [])
|
||||
|
||||
const handleApplyFiltersAndSorting = useCallback(
|
||||
(close: () => void) => {
|
||||
if (sort === searchParams.get("sort")) {
|
||||
close()
|
||||
}
|
||||
|
||||
const newSearchParams = new URLSearchParams(searchParams)
|
||||
newSearchParams.set("sort", sort)
|
||||
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
`${pathname}?${newSearchParams.toString()}`
|
||||
)
|
||||
close()
|
||||
},
|
||||
[pathname, searchParams, sort]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -65,7 +114,16 @@ export default function FilterAndSortModal({
|
||||
</Subtitle>
|
||||
</header>
|
||||
<div className={styles.sorter}>
|
||||
<HotelSorter />
|
||||
<Select
|
||||
items={sortItems}
|
||||
defaultSelectedKey={
|
||||
searchParams.get("sort") ?? DEFAULT_SORT
|
||||
}
|
||||
label={intl.formatMessage({ id: "Sort by" })}
|
||||
name="sort"
|
||||
showRadioButton
|
||||
onSelect={handleSortSelect}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.divider}>
|
||||
<Divider color="subtle" />
|
||||
@@ -78,7 +136,7 @@ export default function FilterAndSortModal({
|
||||
intent="primary"
|
||||
size="medium"
|
||||
theme="base"
|
||||
onClick={close}
|
||||
onClick={() => handleApplyFiltersAndSorting(close)}
|
||||
>
|
||||
{intl.formatMessage(
|
||||
{ id: "See results" },
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
||||
|
||||
import HotelCardDialogListing from "@/components/HotelReservation/HotelCardDialogListing"
|
||||
import HotelCardListing from "@/components/HotelReservation/HotelCardListing"
|
||||
|
||||
@@ -8,27 +10,18 @@ import styles from "./hotelListing.module.css"
|
||||
import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||
import type { HotelListingProps } from "@/types/components/hotelReservation/selectHotel/map"
|
||||
|
||||
export default function HotelListing({
|
||||
hotels,
|
||||
activeHotelPin,
|
||||
setActiveHotelPin,
|
||||
}: HotelListingProps) {
|
||||
export default function HotelListing({ hotels }: HotelListingProps) {
|
||||
const { activeHotelPin } = useHotelsMapStore()
|
||||
return (
|
||||
<>
|
||||
<div className={styles.hotelListing}>
|
||||
<HotelCardListing
|
||||
hotelData={hotels}
|
||||
type={HotelCardListingTypeEnum.MapListing}
|
||||
activeCard={activeHotelPin}
|
||||
onHotelCardHover={setActiveHotelPin}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.hotelListingMobile} data-open={!!activeHotelPin}>
|
||||
<HotelCardDialogListing
|
||||
hotels={hotels}
|
||||
activeCard={activeHotelPin}
|
||||
onActiveCardChange={setActiveHotelPin}
|
||||
/>
|
||||
<HotelCardDialogListing hotels={hotels} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { env } from "@/env/server"
|
||||
import { getCityCoordinates } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import {
|
||||
fetchAvailableHotels,
|
||||
getFiltersFromHotels,
|
||||
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
|
||||
import { safeTry } from "@/utils/safeTry"
|
||||
|
||||
import { getHotelPins } from "../../HotelCardDialogListing/utils"
|
||||
import SelectHotelMap from "."
|
||||
|
||||
import type {
|
||||
HotelData,
|
||||
NullableHotelData,
|
||||
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||
import type { SelectHotelMapContainerProps } from "@/types/components/hotelReservation/selectHotel/map"
|
||||
|
||||
function isValidHotelData(hotel: NullableHotelData): hotel is HotelData {
|
||||
return hotel !== null && hotel !== undefined
|
||||
}
|
||||
|
||||
export async function SelectHotelMapContainer({
|
||||
city,
|
||||
searchParams,
|
||||
adultsInRoom,
|
||||
childrenInRoom,
|
||||
}: SelectHotelMapContainerProps) {
|
||||
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
|
||||
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
|
||||
|
||||
const fetchAvailableHotelsPromise = safeTry(
|
||||
fetchAvailableHotels({
|
||||
cityId: city.id,
|
||||
roomStayStartDate: searchParams.fromDate,
|
||||
roomStayEndDate: searchParams.toDate,
|
||||
adults: adultsInRoom,
|
||||
children: childrenInRoom,
|
||||
})
|
||||
)
|
||||
|
||||
const [hotels] = await fetchAvailableHotelsPromise
|
||||
|
||||
const validHotels = hotels?.filter(isValidHotelData) || []
|
||||
|
||||
const hotelPins = getHotelPins(validHotels)
|
||||
const filterList = getFiltersFromHotels(validHotels)
|
||||
const cityCoordinates = await getCityCoordinates({
|
||||
city: city.name,
|
||||
hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress },
|
||||
})
|
||||
|
||||
return (
|
||||
<SelectHotelMap
|
||||
apiKey={googleMapsApiKey}
|
||||
hotelPins={hotelPins}
|
||||
mapId={googleMapId}
|
||||
hotels={validHotels}
|
||||
filterList={filterList}
|
||||
cityCoordinates={cityCoordinates}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
.container {
|
||||
max-width: var(--max-width);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.listingContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.skeletonContainer {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 20px;
|
||||
gap: var(--Spacing-x2);
|
||||
padding-top: var(--Spacing-x6);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.skeletonItem {
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
.mapContainer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
height: 100%;
|
||||
}
|
||||
.listingContainer {
|
||||
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||
padding: var(--Spacing-x3) var(--Spacing-x4);
|
||||
overflow-y: auto;
|
||||
max-width: 505px;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.skeletonContainer {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
|
||||
import { RoomCardSkeleton } from "../../SelectRate/RoomSelection/RoomCard/RoomCardSkeleton"
|
||||
|
||||
import styles from "./SelectHotelMapContainerSkeleton.module.css"
|
||||
|
||||
type Props = {
|
||||
count?: number
|
||||
}
|
||||
|
||||
export async function SelectHotelMapContainerSkeleton({ count = 2 }: Props) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.listingContainer}>
|
||||
<div className={styles.skeletonContainer}>
|
||||
{Array.from({ length: count }).map((_, index) => (
|
||||
<div key={index} className={styles.skeletonItem}>
|
||||
<RoomCardSkeleton />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.mapContainer}>
|
||||
<SkeletonShimmer width={"100%"} height="100%" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client"
|
||||
|
||||
import { APIProvider } from "@vis.gl/react-google-maps"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
import { useMediaQuery } from "usehooks-ts"
|
||||
|
||||
import { selectHotel } from "@/constants/routes/hotelReservation"
|
||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
||||
|
||||
import { CloseIcon, CloseLargeIcon } from "@/components/Icons"
|
||||
import InteractiveMap from "@/components/Maps/InteractiveMap"
|
||||
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import FilterAndSortModal from "../FilterAndSortModal"
|
||||
@@ -28,17 +31,13 @@ export default function SelectHotelMap({
|
||||
filterList,
|
||||
cityCoordinates,
|
||||
}: SelectHotelMapProps) {
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const lang = useLang()
|
||||
const intl = useIntl()
|
||||
const isAboveMobile = useMediaQuery("(min-width: 768px)")
|
||||
const [activeHotelPin, setActiveHotelPin] = useState<string | null>(null)
|
||||
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
|
||||
const listingContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const selectHotelParams = new URLSearchParams(searchParams.toString())
|
||||
const selectedHotel = selectHotelParams.get("selectedHotel")
|
||||
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
||||
const { activeHotelCard, activeHotelPin } = useHotelsMapStore()
|
||||
|
||||
const coordinates = isAboveMobile
|
||||
? cityCoordinates
|
||||
@@ -52,13 +51,7 @@ export default function SelectHotelMap({
|
||||
activeElement.scrollIntoView({ behavior: "smooth", block: "nearest" })
|
||||
}
|
||||
}
|
||||
}, [activeHotelPin])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedHotel) {
|
||||
setActiveHotelPin(selectedHotel)
|
||||
}
|
||||
}, [selectedHotel])
|
||||
}, [activeHotelCard, activeHotelPin])
|
||||
|
||||
useEffect(() => {
|
||||
const hotelListingElement = document.querySelector(
|
||||
@@ -82,9 +75,15 @@ export default function SelectHotelMap({
|
||||
hotelListingElement?.scrollTo({ top: 0, behavior: "smooth" })
|
||||
}
|
||||
|
||||
function handlePageRedirect() {
|
||||
router.push(`${selectHotel(lang)}?${searchParams.toString()}`)
|
||||
}
|
||||
const filteredHotelPins = useMemo(
|
||||
() =>
|
||||
hotelPins.filter((hotel) =>
|
||||
activeFilters.every((filterId) =>
|
||||
hotel.facilityIds.includes(Number(filterId))
|
||||
)
|
||||
),
|
||||
[activeFilters, hotelPins]
|
||||
)
|
||||
|
||||
const closeButton = (
|
||||
<Button
|
||||
@@ -92,10 +91,12 @@ export default function SelectHotelMap({
|
||||
size="small"
|
||||
theme="base"
|
||||
className={styles.closeButton}
|
||||
onClick={handlePageRedirect}
|
||||
asChild
|
||||
>
|
||||
<CloseIcon color="burgundy" />
|
||||
{intl.formatMessage({ id: "Close the map" })}
|
||||
<Link href={selectHotel(lang)} keepSearchParams>
|
||||
<CloseIcon color="burgundy" />
|
||||
{intl.formatMessage({ id: "Close the map" })}
|
||||
</Link>
|
||||
</Button>
|
||||
)
|
||||
return (
|
||||
@@ -108,26 +109,24 @@ export default function SelectHotelMap({
|
||||
size="small"
|
||||
variant="icon"
|
||||
wrapping
|
||||
onClick={handlePageRedirect}
|
||||
className={styles.filterContainerCloseButton}
|
||||
asChild
|
||||
>
|
||||
<CloseLargeIcon />
|
||||
<Link href={selectHotel(lang)} keepSearchParams>
|
||||
<CloseLargeIcon />
|
||||
</Link>
|
||||
</Button>
|
||||
<FilterAndSortModal filters={filterList} />
|
||||
</div>
|
||||
<HotelListing
|
||||
hotels={hotels}
|
||||
activeHotelPin={activeHotelPin}
|
||||
setActiveHotelPin={setActiveHotelPin}
|
||||
/>
|
||||
{showBackToTop && <BackToTopButton onClick={scrollToTop} />}
|
||||
<HotelListing hotels={hotels} />
|
||||
{showBackToTop && (
|
||||
<BackToTopButton position="left" onClick={scrollToTop} />
|
||||
)}
|
||||
</div>
|
||||
<InteractiveMap
|
||||
closeButton={closeButton}
|
||||
coordinates={coordinates}
|
||||
hotelPins={hotelPins}
|
||||
activeHotelPin={activeHotelPin}
|
||||
onActiveHotelPinChange={setActiveHotelPin}
|
||||
hotelPins={filteredHotelPins}
|
||||
mapId={mapId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||
padding: 0 var(--Spacing-x2);
|
||||
height: 44px;
|
||||
@@ -49,5 +45,6 @@
|
||||
.filterContainer {
|
||||
justify-content: flex-end;
|
||||
padding: 0 0 var(--Spacing-x1);
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user