diff --git a/src/api/travel/places.api.ts b/src/api/travel/places.api.ts index 507f9da..c143b60 100644 --- a/src/api/travel/places.api.ts +++ b/src/api/travel/places.api.ts @@ -9,6 +9,7 @@ export type SearchPlacesParams = { pageNo?: number; numOfRows?: number; arrange?: 'O' | 'Q' | 'R' | 'S'; + keyword?: string; _type?: string; }; diff --git a/src/pages/explore/Filter.tsx b/src/pages/explore/Filter.tsx index c4c82fc..defea24 100644 --- a/src/pages/explore/Filter.tsx +++ b/src/pages/explore/Filter.tsx @@ -37,13 +37,25 @@ export default function FilterPage() { }, [phase, step]); const handleFinish = () => { + // 1. 먼저 /searching 으로 이동 + navigate('/searching'); + + // 2. 3초(3000ms) 후 /search/result로 이동 + setTimeout(() => { navigate('/search/result', { state: { - region: { areaCode: regionCodes.areaCode, sigunguCode: regionCodes.sigunguCode }, - activity: { cat1: activityCodes.cat1, cat2: activityCodes.cat2 }, + region: { + areaCode: regionCodes.areaCode, + sigunguCode: regionCodes.sigunguCode, + }, + activity: { + cat1: activityCodes.cat1, + cat2: activityCodes.cat2, + }, }, }); - }; + }, 3000); +}; const resetAndSelectAgain = () => { setPhase('select'); diff --git a/src/pages/home/TravelSearch.tsx b/src/pages/home/TravelSearch.tsx index 43c85bb..7d0d8a6 100644 --- a/src/pages/home/TravelSearch.tsx +++ b/src/pages/home/TravelSearch.tsx @@ -4,63 +4,62 @@ import Header from '@/component/Header'; import Sidebar from '@/component/SideBar'; import SearchIcon from '@/image/Search.svg'; import PlaceCard from '@/component/common/Card/PlaceCard'; -import SearchingPage from '../explore/Searching'; import { searchPlaces, mapToCard, type PlaceDto } from '@/api/travel/places.api'; +import SortPillSelect from '@/component/selector/SortPillSelect'; type NavState = { region?: { areaCode?: number | string; sigunguCode?: number | string }; activity?: { cat1?: string; cat2?: string[] }; }; const PAGE_SIZE = 20; +const LEGACY_ARRANGE_OPTIONS = [ + { value: 'O', label: '기본순' }, + { value: 'Q', label: '수정일순' }, + { value: 'R', label: '등록일순' }, + { value: 'S', label: '한적함순' }, +] as const; +type LegacyArrange = (typeof LEGACY_ARRANGE_OPTIONS)[number]['value']; export default function TravelSearch() { const navigate = useNavigate(); const { state } = useLocation(); const navState = (state || {}) as NavState; - const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [q, setQ] = useState(''); const [items, setItems] = useState[]>([]); const [, setPageNo] = useState(1); const [hasMore, setHasMore] = useState(true); const [loading, setLoading] = useState(false); - const [initialLoading, setInitialLoading] = useState(false); const [err, setErr] = useState(null); - + const [arrange, setArrange] = useState('O'); const handleMenuClick = () => setIsSidebarOpen(true); const handleCloseSidebar = () => setIsSidebarOpen(false); useEffect(() => { - let alive = true; (async () => { setItems([]); setPageNo(1); setHasMore(true); setErr(null); - - setInitialLoading(true); try { await loadPage(1, true); - } finally { - if (alive) setInitialLoading(false); + } catch (e) { + console.error(e); } })(); - return () => { - alive = false; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ navState?.region?.areaCode, navState?.region?.sigunguCode, navState?.activity?.cat1, JSON.stringify(navState?.activity?.cat2 || []), + arrange, + q, ]); - async function loadPage(p: number, replace = false) { if (loading || (!hasMore && !replace)) return; - if (!replace) setLoading(true); + setLoading(true); setErr(null); - try { const params = { areaCode: navState.region?.areaCode, @@ -69,72 +68,85 @@ export default function TravelSearch() { cat2: navState.activity?.cat2?.[0], pageNo: p, numOfRows: PAGE_SIZE, - arrange: 'O' as const, - }; + arrange, + keyword: q.trim() || undefined, + } as const; const list: PlaceDto[] = await searchPlaces(params); const mapped = list.map(mapToCard); - setItems((prev) => (replace ? mapped : [...prev, ...mapped])); setPageNo(p); setHasMore(list.length >= PAGE_SIZE); } catch { setErr('여행지 목록을 불러오지 못했어요.'); } finally { - if (!replace) setLoading(false); + setLoading(false); } } - - const filtered = useMemo(() => { + const visible = useMemo(() => { const kw = q.trim().toLowerCase(); if (!kw) return items; return items.filter((it) => it.title.toLowerCase().includes(kw)); }, [items, q]); - if (initialLoading) return ; - return (
-
-
+
+
여행지 탐색
- -
- setQ(e.target.value)} - className="bg-gray2 placeholder:text-green1 w-full rounded-full px-4 py-2 pl-10 text-sm text-black focus:outline-none" - /> - - search - +
+
+ setQ(e.target.value)} + className="bg-gray2 placeholder:text-green1 w-full rounded-full px-4 py-2 pl-16 text-sm text-black focus:outline-none" + onKeyDown={(e) => { + if (e.key === 'Enter') loadPage(1, true); + }} + /> + + search + +
+
+ setArrange(v as LegacyArrange)} + size="sm" + /> +
- - {err && ( + {loading && ( +
+ 검색 중입니다... +
+ )} + {err && !loading && (
{err}
)} - {!err && filtered.length === 0 && ( + {!err && !loading && visible.length === 0 && (
조건에 맞는 결과가 없어요.
)} - -
- {filtered.map((it) => ( - navigate(`/place/${it.id}`)} - /> - ))} +
+ {!loading && + visible.map((it) => ( + navigate(`/place/${it.id}`)} + /> + ))}
diff --git a/src/routes/router.tsx b/src/routes/router.tsx index c12a0a6..7f2829e 100644 --- a/src/routes/router.tsx +++ b/src/routes/router.tsx @@ -16,6 +16,7 @@ import Register3 from '@/pages/register/Register3'; import TravelSearch from '@/pages/home/TravelSearch'; import RequireAuth from './RequireAuth'; import Resgister1 from '@/pages/register/Register'; +import SearchingPage from '@/pages/explore/Searching'; export const router = createBrowserRouter([ { @@ -86,4 +87,8 @@ export const router = createBrowserRouter([ path: '/search/result', element: , }, + { + path: '/searching', + element: , + } ]);