From ab7b826cd2c35fda3877c5c1bca55571d2597c24 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 28 Jan 2025 14:44:37 +0100 Subject: [PATCH] feat(SW-718) created useRoomFilteringStore --- .../SelectRate/RoomSelectionPanel/index.tsx | 27 +++-- .../SelectRate/RoomTypeFilter/index.tsx | 22 ++-- .../SelectRate/Rooms/index.tsx | 29 +++-- hooks/selectRate/useRoomFiltering.ts | 92 ---------------- stores/select-rate/room-filtering.ts | 104 ++++++++++++++++++ .../hotelReservation/selectRate/roomFilter.ts | 2 +- .../selectRate/roomSelection.ts | 5 - 7 files changed, 144 insertions(+), 137 deletions(-) delete mode 100644 hooks/selectRate/useRoomFiltering.ts create mode 100644 stores/select-rate/room-filtering.ts diff --git a/components/HotelReservation/SelectRate/RoomSelectionPanel/index.tsx b/components/HotelReservation/SelectRate/RoomSelectionPanel/index.tsx index e6ef1dc03..1b6053f0b 100644 --- a/components/HotelReservation/SelectRate/RoomSelectionPanel/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelectionPanel/index.tsx @@ -1,6 +1,8 @@ import { useSearchParams } from "next/navigation" import { useMemo } from "react" +import { useRoomFilteringStore } from "@/stores/select-rate/room-filtering" + import RoomTypeFilter from "../RoomTypeFilter" import RoomTypeList from "../RoomTypeList" @@ -8,16 +10,17 @@ import type { FilterValues } from "@/types/components/hotelReservation/selectRat import type { RoomSelectionPanelProps } from "@/types/components/hotelReservation/selectRate/roomSelection" export function RoomSelectionPanel({ - rooms, roomCategories, availablePackages, selectedPackages, hotelType, - handleFilter, defaultPackages, roomListIndex, }: RoomSelectionPanelProps) { const searchParams = useSearchParams() + const { getRooms } = useRoomFilteringStore() + + const rooms = getRooms(roomListIndex) const initialFilterValues = useMemo(() => { const packagesFromSearchParams = @@ -32,19 +35,21 @@ export function RoomSelectionPanel({ return ( <> - + {rooms && ( + + )} ) } diff --git a/components/HotelReservation/SelectRate/RoomTypeFilter/index.tsx b/components/HotelReservation/SelectRate/RoomTypeFilter/index.tsx index d6c36b822..10de5d1fd 100644 --- a/components/HotelReservation/SelectRate/RoomTypeFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomTypeFilter/index.tsx @@ -7,6 +7,8 @@ import { useIntl } from "react-intl" import { useMediaQuery } from "usehooks-ts" import { z } from "zod" +import { useRoomFilteringStore } from "@/stores/select-rate/room-filtering" + import { getIconForFeatureCode } from "@/components/HotelReservation/utils" import { InfoCircleIcon } from "@/components/Icons" import CheckboxChip from "@/components/TempDesignSystem/Form/FilterChip/Checkbox" @@ -24,13 +26,12 @@ import { export default function RoomFilter({ numberOfRooms, - onFilter, filterOptions, initialFilterValues, + roomListIndex, }: RoomFilterProps) { const isTabletAndUp = useMediaQuery("(min-width: 768px)") const [isAboveMobile, setIsAboveMobile] = useState(false) - const [isInitialized, setIsInitialized] = useState(false) const intl = useIntl() const methods = useForm({ @@ -40,6 +41,8 @@ export default function RoomFilter({ resolver: zodResolver(z.object({})), }) + const { handleFilter } = useRoomFilteringStore() + const { watch, getValues } = methods const petFriendly = watch(RoomPackageCodeEnum.PET_ROOM) const allergyFriendly = watch(RoomPackageCodeEnum.ALLERGY_ROOM) @@ -51,23 +54,18 @@ export default function RoomFilter({ }) useEffect(() => { - if (!initialFilterValues || isInitialized) return + if (!initialFilterValues) return - onFilter(initialFilterValues) - setIsInitialized(true) - }, [initialFilterValues, onFilter, isInitialized]) + handleFilter(initialFilterValues, roomListIndex) + }, [initialFilterValues, handleFilter, roomListIndex]) // Watch for filter changes useEffect(() => { const subscription = watch((value, { name }) => { - if (!name || !isInitialized) return - - const currentValues = getValues() - onFilter(currentValues) + if (name) handleFilter(getValues(), roomListIndex) }) - return () => subscription.unsubscribe() - }, [watch, getValues, onFilter, isInitialized]) + }, [watch, getValues, handleFilter, roomListIndex]) useEffect(() => { setIsAboveMobile(isTabletAndUp) diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 2fe4aea3a..87dc6c08d 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -1,13 +1,13 @@ "use client" import { usePathname, useRouter, useSearchParams } from "next/navigation" -import { useCallback, useEffect, useMemo } from "react" +import { useEffect, useMemo } from "react" import { useIntl } from "react-intl" import { useRateSelectionStore } from "@/stores/select-rate/rate-selection" +import { useRoomFilteringStore } from "@/stores/select-rate/room-filtering" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" -import { useRoomFiltering } from "@/hooks/selectRate/useRoomFiltering" import { trackLowestRoomPrice } from "@/utils/tracking" import { convertObjToSearchParams, convertSearchParamsToObj } from "@/utils/url" @@ -45,6 +45,13 @@ export default function Rooms({ const { selectedRates, rateSummary, calculateRateSummary, initializeRates } = useRateSelectionStore() + const { + selectedPackagesByRoom, + setVisibleRooms, + setRoomsAvailability, + getFilteredRooms, + } = useRoomFilteringStore() + const bookingWidgetSearchData = useMemo( () => convertSearchParamsToObj( @@ -109,8 +116,10 @@ export default function Rooms({ [availablePackages, intl] ) - const { selectedPackagesByRoom, getRooms, handleFilter, getFilteredRooms } = - useRoomFiltering({ roomsAvailability }) + useEffect(() => { + setRoomsAvailability(roomsAvailability) + setVisibleRooms() + }, [roomsAvailability, setRoomsAvailability, setVisibleRooms]) useEffect(() => { if ( @@ -183,14 +192,6 @@ export default function Rooms({ router.push(`select-bed?${queryParams}`) } - const handleFilterForRoom = useCallback( - (index: number) => - (filter: Record) => { - handleFilter(filter, index) - }, - [handleFilter] - ) - useEffect(() => { setTimeout(() => { const SCROLL_OFFSET = 100 @@ -252,12 +253,10 @@ export default function Rooms({
@@ -268,12 +267,10 @@ export default function Rooms({ }) ) : ( diff --git a/hooks/selectRate/useRoomFiltering.ts b/hooks/selectRate/useRoomFiltering.ts deleted file mode 100644 index 734c01bae..000000000 --- a/hooks/selectRate/useRoomFiltering.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { useCallback, useMemo, useState } from "react" - -import { filterDuplicateRoomTypesByLowestPrice } from "@/components/HotelReservation/SelectRate/Rooms/utils" - -import type { - RoomPackageCodeEnum, - RoomPackageCodes, -} from "@/types/components/hotelReservation/selectRate/roomFilter" -import type { - RoomConfiguration, - RoomsAvailability, -} from "@/server/routers/hotels/output" - -interface UseRoomFilteringProps { - roomsAvailability: RoomsAvailability -} - -export function useRoomFiltering({ roomsAvailability }: UseRoomFilteringProps) { - const [selectedPackagesByRoom, setSelectedPackagesByRoom] = useState< - Record - >({}) - - const visibleRooms = useMemo(() => { - const deduped = filterDuplicateRoomTypesByLowestPrice( - roomsAvailability.roomConfigurations - ) - - const separated = deduped.reduce<{ - available: RoomConfiguration[] - notAvailable: RoomConfiguration[] - }>( - (acc, curr) => { - if (curr.status === "NotAvailable") - return { ...acc, notAvailable: [...acc.notAvailable, curr] } - return { ...acc, available: [...acc.available, curr] } - }, - { available: [], notAvailable: [] } - ) - - return [...separated.available, ...separated.notAvailable] - }, [roomsAvailability.roomConfigurations]) - - const handleFilter = useCallback( - ( - filter: Record, - roomIndex: number - ) => { - const filteredPackages = Object.keys(filter).filter( - (key) => filter[key as RoomPackageCodeEnum] - ) as RoomPackageCodeEnum[] - - setSelectedPackagesByRoom((prev) => ({ - ...prev, - [roomIndex]: filteredPackages, - })) - }, - [] - ) - - const getFilteredRooms = useCallback( - (roomIndex: number) => { - const selectedPackages = selectedPackagesByRoom[roomIndex] || [] - - return visibleRooms.filter((room) => - selectedPackages.every((filteredPackage) => - room.features.some((feature) => feature.code === filteredPackage) - ) - ) - }, - [visibleRooms, selectedPackagesByRoom] - ) - const getRooms = useCallback( - (roomIndex: number) => { - const selectedPackages = selectedPackagesByRoom[roomIndex] || [] - const filteredRooms = getFilteredRooms(roomIndex) - - return { - ...roomsAvailability, - roomConfigurations: - selectedPackages.length === 0 ? visibleRooms : filteredRooms, - } - }, - [roomsAvailability, visibleRooms, selectedPackagesByRoom, getFilteredRooms] - ) - - return { - selectedPackagesByRoom, - getFilteredRooms, - getRooms, - handleFilter, - } -} diff --git a/stores/select-rate/room-filtering.ts b/stores/select-rate/room-filtering.ts new file mode 100644 index 000000000..9454ce692 --- /dev/null +++ b/stores/select-rate/room-filtering.ts @@ -0,0 +1,104 @@ +import { create } from "zustand" + +import { filterDuplicateRoomTypesByLowestPrice } from "@/components/HotelReservation/SelectRate/Rooms/utils" + +import type { + FilterValues, + RoomPackageCodeEnum, + RoomPackageCodes, +} from "@/types/components/hotelReservation/selectRate/roomFilter" +import type { + RoomConfiguration, + RoomsAvailability, +} from "@/server/routers/hotels/output" + +interface RoomFilteringState { + selectedPackagesByRoom: Record + roomsAvailability: RoomsAvailability | null + visibleRooms: RoomConfiguration[] + setVisibleRooms: () => void + setRoomsAvailability: (rooms: RoomsAvailability) => void + handleFilter: (filter: FilterValues, roomIndex: number) => void + getFilteredRooms: (roomIndex: number) => RoomConfiguration[] + getRooms: (roomIndex: number) => RoomsAvailability | null +} + +export const useRoomFilteringStore = create((set, get) => ({ + selectedPackagesByRoom: {}, + roomsAvailability: null, + visibleRooms: [], + setRoomsAvailability: (rooms) => { + set({ roomsAvailability: rooms }) + }, + setVisibleRooms: () => { + const { roomsAvailability } = get() + if (!roomsAvailability) return null + + const deduped = filterDuplicateRoomTypesByLowestPrice( + roomsAvailability.roomConfigurations + ) + + const separated = deduped.reduce<{ + available: RoomConfiguration[] + notAvailable: RoomConfiguration[] + }>( + (acc, curr) => { + if (curr.status === "NotAvailable") + return { ...acc, notAvailable: [...acc.notAvailable, curr] } + return { ...acc, available: [...acc.available, curr] } + }, + { available: [], notAvailable: [] } + ) + + set({ visibleRooms: [...separated.available, ...separated.notAvailable] }) + }, + + handleFilter: (filter, roomIndex) => { + const filteredPackages = Object.entries(filter) + .filter(([_, isSelected]) => isSelected) + .map(([code]) => code) as RoomPackageCodeEnum[] + + set((state) => { + const currentPackages = state.selectedPackagesByRoom[roomIndex] || [] + const sortedCurrent = [...currentPackages].sort() + const sortedNew = [...filteredPackages].sort() + + if (JSON.stringify(sortedCurrent) === JSON.stringify(sortedNew)) { + return state + } + + return { + ...state, + selectedPackagesByRoom: { + ...state.selectedPackagesByRoom, + [roomIndex]: filteredPackages, + }, + } + }) + }, + + getFilteredRooms: (roomIndex) => { + const state = get() + const selectedPackages = state.selectedPackagesByRoom[roomIndex] || [] + + return state.visibleRooms.filter((room) => + selectedPackages.every((filteredPackage) => + room.features.some((feature) => feature.code === filteredPackage) + ) + ) + }, + + getRooms: (roomIndex) => { + const state = get() + if (!state.roomsAvailability) return null + + const selectedPackages = state.selectedPackagesByRoom[roomIndex] || [] + const filteredRooms = state.getFilteredRooms(roomIndex) + + return { + ...state.roomsAvailability, + roomConfigurations: + selectedPackages.length === 0 ? state.visibleRooms : filteredRooms, + } + }, +})) diff --git a/types/components/hotelReservation/selectRate/roomFilter.ts b/types/components/hotelReservation/selectRate/roomFilter.ts index 9dc5af31f..981ea0784 100644 --- a/types/components/hotelReservation/selectRate/roomFilter.ts +++ b/types/components/hotelReservation/selectRate/roomFilter.ts @@ -19,9 +19,9 @@ export interface FilterValues { } export interface RoomFilterProps { numberOfRooms: number - onFilter: (filter: Record) => void filterOptions: DefaultFilterOptions[] initialFilterValues: FilterValues + roomListIndex: number } export type RoomPackage = z.output diff --git a/types/components/hotelReservation/selectRate/roomSelection.ts b/types/components/hotelReservation/selectRate/roomSelection.ts index 4f0f3c978..014f3a731 100644 --- a/types/components/hotelReservation/selectRate/roomSelection.ts +++ b/types/components/hotelReservation/selectRate/roomSelection.ts @@ -3,7 +3,6 @@ import type { RoomsAvailability } from "@/server/routers/hotels/output" import type { DefaultFilterOptions, RoomPackage, - RoomPackageCodeEnum, RoomPackageCodes, RoomPackageData, } from "./roomFilter" @@ -26,14 +25,10 @@ export interface SelectRateProps { } export interface RoomSelectionPanelProps { - rooms: RoomsAvailability roomCategories: RoomData[] availablePackages: RoomPackage[] selectedPackages: RoomPackageCodes[] hotelType: string | undefined - handleFilter: ( - filter: Record - ) => void defaultPackages: DefaultFilterOptions[] roomListIndex: number }