From 0cce59619a7566295d829667cb1f5eb5dc804eda Mon Sep 17 00:00:00 2001 From: zzzryt Date: Sun, 7 Dec 2025 22:26:27 +0900 Subject: [PATCH] =?UTF-8?q?(#197):=20tour=20filter=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EB=A1=9C=EC=BB=AC=20=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router/Router.tsx | 1 + .../aroundTourist/ui/GeoAroundTouristMap.tsx | 25 ++++---- src/features/map/lib/withAroundMapParams.tsx | 6 +- src/features/tour/lib/withGeoTripParams.tsx | 28 ++++----- src/features/tour/types.ts | 5 ++ src/features/tourFilter/hook/index.ts | 1 - .../tourFilter/hook/useTourFilterQuery.ts | 37 ------------ src/features/tourFilter/index.ts | 1 - src/features/tourFilter/ui/DistanceSlider.tsx | 25 +++++--- .../tourFilter/ui/TourFilterSidebar.tsx | 60 ++++++++++--------- .../ui/TouristFilterQueryUpdater.tsx | 24 +++----- .../tourList/ui/TourListContainer.tsx | 14 +---- .../tourShort/ui/TourSwiperContainer.tsx | 13 +--- src/pages/tour/geotrip/GeoTrip.tsx | 13 ++-- src/shared/ui/TouristContentsTypeFilter.tsx | 12 +++- src/widgets/bottomNavigationBar/index.tsx | 6 +- src/widgets/bottomNavigationBar/utils.ts | 11 +--- 17 files changed, 117 insertions(+), 165 deletions(-) delete mode 100644 src/features/tourFilter/hook/index.ts delete mode 100644 src/features/tourFilter/hook/useTourFilterQuery.ts diff --git a/src/app/router/Router.tsx b/src/app/router/Router.tsx index c0c79df7..41a51031 100644 --- a/src/app/router/Router.tsx +++ b/src/app/router/Router.tsx @@ -28,6 +28,7 @@ export default function Router() { } /> }> + } /> } /> } /> } /> diff --git a/src/features/aroundTourist/ui/GeoAroundTouristMap.tsx b/src/features/aroundTourist/ui/GeoAroundTouristMap.tsx index 46287e71..e7a5daa5 100644 --- a/src/features/aroundTourist/ui/GeoAroundTouristMap.tsx +++ b/src/features/aroundTourist/ui/GeoAroundTouristMap.tsx @@ -1,4 +1,4 @@ -import { useMemo, useRef, useState } from 'react'; +import { useMemo, useRef } from 'react'; import { useQuery } from '@tanstack/react-query'; import { Map } from 'react-kakao-maps-sdk'; @@ -10,26 +10,25 @@ import { NearbyTouristAttractionPinPoint, MiddleContent, } from '@/features/aroundTourist'; -import { TouristContentsTypeFilter } from '@/shared'; +import { TouristContentsTypeFilter, useLocalStorage } from '@/shared'; -import type { AroundContentTypeId, TourItem } from '@/entities/tour'; +import type { TourItem } from '@/entities/tour'; import type { GeoTripLocation } from '@/shared'; +import type { TourInjected } from '@/features/tour'; interface GeoAroundTouristMapProps { location: GeoTripLocation; contentId: string; - tourContentTypeId: AroundContentTypeId; } -function GeoAroundTouristMap({ - location, - tourContentTypeId, -}: GeoAroundTouristMapProps) { - const [selectedContentTypeId, setSelectedContentTypeId] = - useState(tourContentTypeId); +function GeoAroundTouristMap({ location }: GeoAroundTouristMapProps) { + const [tourFilter, setTourFilter] = useLocalStorage('tourInfo', { + distance: '20000', + contentTypeId: '12', + } as TourInjected); const { data: aroundTouristObjects = [] } = useQuery( - aroundTouristQueries.list(location, selectedContentTypeId), + aroundTouristQueries.list(location, tourFilter.contentTypeId), ); const middleTouristRef = useRef(null); @@ -53,8 +52,8 @@ function GeoAroundTouristMap({
diff --git a/src/features/map/lib/withAroundMapParams.tsx b/src/features/map/lib/withAroundMapParams.tsx index 243994dc..09b7b6de 100644 --- a/src/features/map/lib/withAroundMapParams.tsx +++ b/src/features/map/lib/withAroundMapParams.tsx @@ -19,16 +19,13 @@ export default function withAroundMapParams

( const mapx = searchParams.get('lng'); const mapy = searchParams.get('lat'); const contentId = searchParams.get('contentId'); - const tourContentTypeId = searchParams.get( - 'contentTypeId', - ) as AroundContentTypeId; const location: GeoTripLocation = { lat: mapy ? Number(mapy) : 0, lng: mapx ? Number(mapx) : 0, }; - if (!location || !contentId || !tourContentTypeId) { + if (!location || !contentId) { return (

필요한 정보가 부족합니다. 위치, 콘텐츠 ID, 관광지 타입을 확인해주세요. @@ -41,7 +38,6 @@ export default function withAroundMapParams

( {...(props as P)} location={location} contentId={contentId} - tourContentTypeId={tourContentTypeId} /> ); }; diff --git a/src/features/tour/lib/withGeoTripParams.tsx b/src/features/tour/lib/withGeoTripParams.tsx index 05758447..be41772a 100644 --- a/src/features/tour/lib/withGeoTripParams.tsx +++ b/src/features/tour/lib/withGeoTripParams.tsx @@ -1,36 +1,32 @@ -import { useSearchParams } from 'react-router-dom'; - import { isValidTourType } from '@/features/map'; -import type { AroundContentTypeId } from '@/entities/tour'; -interface InjectedProps { - distance: string; - tourContentTypeId: AroundContentTypeId; -} +import { useLocalStorage } from '@/shared'; +import type { TourInjected } from '../types'; -export function withGeoTripParams

( +export function withGeoTripParams

( WrappedComponent: React.ComponentType

, ) { - return function GeoTripWrapper(props: Omit) { - const [searchParams] = useSearchParams(); - const distance = searchParams.get('distance'); - const tourContentTypeId = searchParams.get('tour-type'); + return function GeoTripWrapper(props: Omit) { + const [tourInfo] = useLocalStorage('tourInfo', { + distance: '20000', + contentTypeId: '12', + }); - if (!distance || !tourContentTypeId) { + if (!tourInfo.distance || !tourInfo.contentTypeId) { throw new Error( '필요한 정보가 부족합니다. 거리와 관광지 타입을 확인해주세요.', ); } - if (!isValidTourType(tourContentTypeId)) { + if (!isValidTourType(tourInfo.contentTypeId)) { throw new Error('잘못된 관광 타입입니다.'); } return ( ); }; diff --git a/src/features/tour/types.ts b/src/features/tour/types.ts index f17c25ed..d0b130f6 100644 --- a/src/features/tour/types.ts +++ b/src/features/tour/types.ts @@ -33,3 +33,8 @@ export type TourDetailImage = { originimgurl?: string; serialnum: string; }; + +export type TourInjected = { + distance: string; + contentTypeId: AroundContentTypeId; +}; diff --git a/src/features/tourFilter/hook/index.ts b/src/features/tourFilter/hook/index.ts deleted file mode 100644 index d8254b45..00000000 --- a/src/features/tourFilter/hook/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useTourFilterQuery'; diff --git a/src/features/tourFilter/hook/useTourFilterQuery.ts b/src/features/tourFilter/hook/useTourFilterQuery.ts deleted file mode 100644 index 139f631c..00000000 --- a/src/features/tourFilter/hook/useTourFilterQuery.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useSearchParams } from 'react-router-dom'; - -import { isValidTourType } from '@/features/map'; -import { isValidDistance } from '@/features/tourFilter'; - -import type { Distance } from '@/features/tourFilter'; -import type { AroundContentTypeId } from '@/entities/tour'; - -type UpdateType = { - tourType: AroundContentTypeId; - distance: Distance; -}; - -export const useTourFilterQuery = () => { - const [searchParams, setSearchParams] = useSearchParams(); - - const updateQuery = ({ tourType, distance }: UpdateType) => { - const nextParams = new URLSearchParams(searchParams); - nextParams.set('tour-type', tourType); - nextParams.set('distance', String(distance * 1000)); - // nextParams.set('sort', sortOption); - nextParams.delete('slide-index'); - nextParams.delete('page-param'); - setSearchParams(nextParams, { replace: true }); - }; - - const getQuery = () => { - const tourType = searchParams.get('tour-type'); - const distance = Number(searchParams.get('distance')) / 1000; - - if (isValidTourType(tourType) && isValidDistance(distance)) { - return { tourType, distance }; - } - }; - - return { searchParams, updateQuery, getQuery }; -}; diff --git a/src/features/tourFilter/index.ts b/src/features/tourFilter/index.ts index 40e2e5de..593a8092 100644 --- a/src/features/tourFilter/index.ts +++ b/src/features/tourFilter/index.ts @@ -1,4 +1,3 @@ export * from './ui'; export * from './const'; export * from './type'; -export * from './hook'; diff --git a/src/features/tourFilter/ui/DistanceSlider.tsx b/src/features/tourFilter/ui/DistanceSlider.tsx index c0eef5f4..ab017f3f 100644 --- a/src/features/tourFilter/ui/DistanceSlider.tsx +++ b/src/features/tourFilter/ui/DistanceSlider.tsx @@ -1,28 +1,37 @@ -import type { Dispatch } from 'react'; -import type { Distance } from '@/features/tourFilter'; +import type { TourInjected } from '@/features/tour/types'; interface DistanceSliderProps { - distance: number; - setDistance: Dispatch>; + distance: string; + setDistance: ( + value: TourInjected | ((val: TourInjected) => TourInjected), + ) => void; } export default function DistanceSlider({ distance, setDistance, }: DistanceSliderProps) { + const handleDistanceChange = (e: React.ChangeEvent) => { + setDistance(prev => ({ + ...prev, + distance: e.target.value, + })); + }; + return ( <>

1km + {distance}m 20km
setDistance(Number(e.target.value) as Distance)} + onChange={handleDistanceChange} className="w-full" /> diff --git a/src/features/tourFilter/ui/TourFilterSidebar.tsx b/src/features/tourFilter/ui/TourFilterSidebar.tsx index 0044dec6..5f8c77f2 100644 --- a/src/features/tourFilter/ui/TourFilterSidebar.tsx +++ b/src/features/tourFilter/ui/TourFilterSidebar.tsx @@ -1,14 +1,9 @@ -import { useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; -import { - DistanceSlider, - SortOptions, - useTourFilterQuery, -} from '@/features/tourFilter'; -import { TouristContentsTypeFilter } from '@/shared'; - -import type { SortOption } from '@/features/tourFilter'; +import { DistanceSlider } from '@/features/tourFilter'; +import { TouristContentsTypeFilter, useLocalStorage } from '@/shared'; +import type { TourInjected } from '@/features/tour/types'; +import { useState } from 'react'; interface BottomSheetProps { onClose: () => void; @@ -19,19 +14,31 @@ export default function TourFilterSidebar({ onClose, isOpen, }: BottomSheetProps) { - const { getQuery, updateQuery } = useTourFilterQuery(); + const [tourFilter, setTourFilter] = useLocalStorage('tourInfo', { + distance: '20000', + contentTypeId: '12', + } as TourInjected); - const [aroundContentTypeId, setAroundContentTypeId] = useState( - getQuery()?.tourType ?? '12', - ); - const [distance, setDistance] = useState(getQuery()?.distance || 1); - const [sortOption, setSortOption] = useState('distance'); + const [currentTourFilter, setCurrentTourFilter] = useState({ + distance: tourFilter.distance, + contentTypeId: tourFilter.contentTypeId, + }); const handleSubmit = () => { - updateQuery({ tourType: aroundContentTypeId, distance }); + setTourFilter({ + distance: currentTourFilter.distance, + contentTypeId: currentTourFilter.contentTypeId, + }); onClose(); }; + const handleReset = () => { + setCurrentTourFilter({ + distance: '20000', + contentTypeId: '12', + }); + }; + return ( {isOpen && ( @@ -58,28 +65,25 @@ export default function TourFilterSidebar({ 관광 타입
- -
-
- -
-
diff --git a/src/shared/ui/TouristContentsTypeFilter.tsx b/src/shared/ui/TouristContentsTypeFilter.tsx index 2541dada..48f4dc7e 100644 --- a/src/shared/ui/TouristContentsTypeFilter.tsx +++ b/src/shared/ui/TouristContentsTypeFilter.tsx @@ -6,10 +6,13 @@ import clsx from 'clsx'; import { markerList } from '@/features/aroundTourist'; import type { AroundContentTypeId } from '@/entities/tour'; +import type { TourInjected } from '@/features/tour'; interface TouristContentsTypeFilterProps { contentTypeId: AroundContentTypeId; - setContentTypeId: React.Dispatch>; + setContentTypeId: ( + value: TourInjected | ((val: TourInjected) => TourInjected), + ) => void; } export default function TouristContentsTypeFilter({ contentTypeId, @@ -37,7 +40,12 @@ export default function TouristContentsTypeFilter({ diff --git a/src/widgets/bottomNavigationBar/index.tsx b/src/widgets/bottomNavigationBar/index.tsx index b5ac9fb5..37b24bde 100644 --- a/src/widgets/bottomNavigationBar/index.tsx +++ b/src/widgets/bottomNavigationBar/index.tsx @@ -1,14 +1,12 @@ -import { useLocation, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import clsx from 'clsx'; import { createNavItems } from '@/widgets/bottomNavigationBar/utils'; export function BottomNavigationBar() { - const location = useLocation(); const navigate = useNavigate(); - const currentParams = new URLSearchParams(location.search); - const navItems = createNavItems({ currentParams, navigate }); + const navItems = createNavItems({ navigate }); const getIconClass = (path: string) => clsx({ 'text-black': location.pathname !== path, diff --git a/src/widgets/bottomNavigationBar/utils.ts b/src/widgets/bottomNavigationBar/utils.ts index af6b78e4..fd4332e5 100644 --- a/src/widgets/bottomNavigationBar/utils.ts +++ b/src/widgets/bottomNavigationBar/utils.ts @@ -7,19 +7,12 @@ import { } from '@/assets/common'; type createNavItemsParams = { - currentParams: URLSearchParams; navigate: (to: To, options?: NavigateOptions) => void | Promise; }; -export const createNavItems = ({ - currentParams, - navigate, -}: createNavItemsParams) => { - const paramsString = currentParams.toString(); - +export const createNavItems = ({ navigate }: createNavItemsParams) => { const navigateTo = (path: string) => { - const destination = paramsString ? `${path}?${paramsString}` : path; - navigate(destination, { replace: true }); + navigate(path, { replace: true }); }; return [