Fix: created hook for useScrollToTop to reuse functionallity and util for getSortedHotels
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
||||||
@@ -8,18 +8,18 @@ import { useHotelsMapStore } from "@/stores/hotels-map"
|
|||||||
|
|
||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
||||||
|
import { useScrollToTop } from "@/hooks/useScrollToTop"
|
||||||
|
|
||||||
import HotelCard from "../HotelCard"
|
import HotelCard from "../HotelCard"
|
||||||
import { DEFAULT_SORT } from "../SelectHotel/HotelSorter"
|
import { DEFAULT_SORT } from "../SelectHotel/HotelSorter"
|
||||||
|
import { getSortedHotels } from "./utils"
|
||||||
|
|
||||||
import styles from "./hotelCardListing.module.css"
|
import styles from "./hotelCardListing.module.css"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type HotelCardListingProps,
|
type HotelCardListingProps,
|
||||||
HotelCardListingTypeEnum,
|
HotelCardListingTypeEnum,
|
||||||
type HotelData,
|
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||||
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
|
|
||||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
export default function HotelCardListing({
|
export default function HotelCardListing({
|
||||||
@@ -29,82 +29,36 @@ export default function HotelCardListing({
|
|||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
||||||
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
|
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
|
||||||
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { activeHotelCard } = useHotelsMapStore()
|
const { activeHotelCard } = useHotelsMapStore()
|
||||||
|
const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490 })
|
||||||
|
|
||||||
const sortBy = useMemo(
|
const sortBy = useMemo(
|
||||||
() => searchParams.get("sort") ?? DEFAULT_SORT,
|
() => searchParams.get("sort") ?? DEFAULT_SORT,
|
||||||
[searchParams]
|
[searchParams]
|
||||||
)
|
)
|
||||||
|
|
||||||
const sortedHotels = useMemo(() => {
|
const sortedHotels = useMemo(
|
||||||
switch (sortBy) {
|
() => getSortedHotels({ hotels: hotelData, sortBy }),
|
||||||
case SortOrder.Name:
|
[hotelData, sortBy]
|
||||||
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:
|
|
||||||
const getPricePerNight = (hotel: HotelData): number => {
|
|
||||||
return (
|
|
||||||
hotel.price?.member?.localPrice?.pricePerNight ??
|
|
||||||
hotel.price?.public?.localPrice?.pricePerNight ??
|
|
||||||
Infinity
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return [...hotelData].sort(
|
|
||||||
(a, b) => getPricePerNight(a) - getPricePerNight(b)
|
|
||||||
)
|
|
||||||
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(() => {
|
||||||
if (activeFilters.length === 0) {
|
if (activeFilters.length === 0) return sortedHotels
|
||||||
return sortedHotels
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredHotels = sortedHotels.filter((hotel) =>
|
return sortedHotels.filter((hotel) =>
|
||||||
activeFilters.every((appliedFilterId) =>
|
activeFilters.every((appliedFilterId) =>
|
||||||
hotel.hotelData.detailedFacilities.some(
|
hotel.hotelData.detailedFacilities.some(
|
||||||
(facility) => facility.id.toString() === appliedFilterId
|
(facility) => facility.id.toString() === appliedFilterId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return filteredHotels
|
|
||||||
}, [activeFilters, sortedHotels])
|
}, [activeFilters, sortedHotels])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
setResultCount(hotels?.length ?? 0)
|
||||||
const hasScrolledPast = window.scrollY > 490
|
|
||||||
setShowBackToTop(hasScrolledPast)
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("scroll", handleScroll, { passive: true })
|
|
||||||
return () => window.removeEventListener("scroll", handleScroll)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setResultCount(hotels ? hotels.length : 0)
|
|
||||||
}, [hotels, setResultCount])
|
}, [hotels, setResultCount])
|
||||||
|
|
||||||
function scrollToTop() {
|
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.hotelCards}>
|
<section className={styles.hotelCards}>
|
||||||
{hotels?.length ? (
|
{hotels?.length ? (
|
||||||
|
|||||||
35
components/HotelReservation/HotelCardListing/utils.ts
Normal file
35
components/HotelReservation/HotelCardListing/utils.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||||
|
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
|
||||||
|
|
||||||
|
export function getSortedHotels({
|
||||||
|
hotels,
|
||||||
|
sortBy,
|
||||||
|
}: {
|
||||||
|
hotels: HotelData[]
|
||||||
|
sortBy: string
|
||||||
|
}) {
|
||||||
|
const getPricePerNight = (hotel: HotelData): number =>
|
||||||
|
hotel.price?.member?.localPrice?.pricePerNight ??
|
||||||
|
hotel.price?.public?.localPrice?.pricePerNight ??
|
||||||
|
Infinity
|
||||||
|
|
||||||
|
const sortingStrategies: Record<
|
||||||
|
string,
|
||||||
|
(a: HotelData, b: HotelData) => number
|
||||||
|
> = {
|
||||||
|
[SortOrder.Name]: (a: HotelData, b: HotelData) =>
|
||||||
|
a.hotelData.name.localeCompare(b.hotelData.name),
|
||||||
|
[SortOrder.TripAdvisorRating]: (a: HotelData, b: HotelData) =>
|
||||||
|
(b.hotelData.ratings?.tripAdvisor.rating ?? 0) -
|
||||||
|
(a.hotelData.ratings?.tripAdvisor.rating ?? 0),
|
||||||
|
[SortOrder.Price]: (a: HotelData, b: HotelData) =>
|
||||||
|
getPricePerNight(a) - getPricePerNight(b),
|
||||||
|
[SortOrder.Distance]: (a: HotelData, b: HotelData) =>
|
||||||
|
a.hotelData.location.distanceToCentre -
|
||||||
|
b.hotelData.location.distanceToCentre,
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...hotels].sort(
|
||||||
|
sortingStrategies[sortBy] ?? sortingStrategies[SortOrder.Distance]
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
|||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
import { useScrollToTop } from "@/hooks/useScrollToTop"
|
||||||
import { debounce } from "@/utils/debounce"
|
import { debounce } from "@/utils/debounce"
|
||||||
|
|
||||||
import FilterAndSortModal from "../../FilterAndSortModal"
|
import FilterAndSortModal from "../../FilterAndSortModal"
|
||||||
@@ -41,13 +42,17 @@ export default function SelectHotelContent({
|
|||||||
|
|
||||||
const isAboveMobile = useMediaQuery("(min-width: 768px)")
|
const isAboveMobile = useMediaQuery("(min-width: 768px)")
|
||||||
const [visibleHotels, setVisibleHotels] = useState<HotelData[]>([])
|
const [visibleHotels, setVisibleHotels] = useState<HotelData[]>([])
|
||||||
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
|
|
||||||
const [showSkeleton, setShowSkeleton] = useState<boolean>(false)
|
const [showSkeleton, setShowSkeleton] = useState<boolean>(false)
|
||||||
const listingContainerRef = useRef<HTMLDivElement | null>(null)
|
const listingContainerRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
||||||
const { activeHotelCard, activeHotelPin } = useHotelsMapStore()
|
const { activeHotelCard, activeHotelPin } = useHotelsMapStore()
|
||||||
|
|
||||||
|
const { showBackToTop, scrollToTop } = useScrollToTop({
|
||||||
|
threshold: 490,
|
||||||
|
elementRef: listingContainerRef,
|
||||||
|
})
|
||||||
|
|
||||||
const coordinates = useMemo(
|
const coordinates = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isAboveMobile
|
isAboveMobile
|
||||||
@@ -66,28 +71,6 @@ export default function SelectHotelContent({
|
|||||||
}
|
}
|
||||||
}, [activeHotelCard, activeHotelPin])
|
}, [activeHotelCard, activeHotelPin])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const hotelListingElement = document.querySelector(
|
|
||||||
`.${styles.listingContainer}`
|
|
||||||
)
|
|
||||||
if (!hotelListingElement) return
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
const hasScrolledPast = hotelListingElement.scrollTop > 490
|
|
||||||
setShowBackToTop(hasScrolledPast)
|
|
||||||
}
|
|
||||||
|
|
||||||
hotelListingElement.addEventListener("scroll", handleScroll)
|
|
||||||
return () => hotelListingElement.removeEventListener("scroll", handleScroll)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
function scrollToTop() {
|
|
||||||
const hotelListingElement = document.querySelector(
|
|
||||||
`.${styles.listingContainer}`
|
|
||||||
)
|
|
||||||
hotelListingElement?.scrollTo({ top: 0, behavior: "smooth" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredHotelPins = useMemo(
|
const filteredHotelPins = useMemo(
|
||||||
() =>
|
() =>
|
||||||
hotelPins.filter((hotel) =>
|
hotelPins.filter((hotel) =>
|
||||||
|
|||||||
33
hooks/useScrollToTop.ts
Normal file
33
hooks/useScrollToTop.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { type RefObject,useEffect,useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
interface UseScrollToTopProps {
|
||||||
|
threshold: number
|
||||||
|
elementRef?: RefObject<HTMLElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useScrollToTop({ threshold, elementRef }: UseScrollToTopProps) {
|
||||||
|
const [showBackToTop, setShowBackToTop] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = elementRef?.current ?? window
|
||||||
|
|
||||||
|
function handleScroll() {
|
||||||
|
const scrollTop = elementRef?.current
|
||||||
|
? elementRef.current.scrollTop
|
||||||
|
: window.scrollY
|
||||||
|
setShowBackToTop(scrollTop > threshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
element.addEventListener("scroll", handleScroll, { passive: true })
|
||||||
|
return () => element.removeEventListener("scroll", handleScroll)
|
||||||
|
}, [threshold, elementRef])
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
|
if (elementRef?.current)
|
||||||
|
elementRef.current.scrollTo({ top: 0, behavior: "smooth" })
|
||||||
|
else window.scrollTo({ top: 0, behavior: "smooth" })
|
||||||
|
}
|
||||||
|
|
||||||
|
return { showBackToTop, scrollToTop }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user