From a6e81598a642615e567c8fae2dac956c6b865986 Mon Sep 17 00:00:00 2001 From: mmjjaa <96505287+mmjjaa@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:08:58 +0900 Subject: [PATCH] =?UTF-8?q?209=20feat=20=EC=A7=80=EB=8F=84=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=80=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B5=9C=EA=B7=BC?= =?UTF-8?q?=EA=B2=80=EC=83=89=EC=96=B4=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20(#215)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 스토리북 QueryClientProvider 설정 * feat: 지도 편지 페이지 최근검색어 기능 개발 완료 --- .../SearchFullScreen/SearchFullScreen.tsx | 56 +++++++++++++------ .../SearchHistoryList/SearchHistoryList.tsx | 13 ++++- src/hooks/useNominatimSearch.ts | 54 +++++++++++++++++- src/pages/Map/MapExplorerPage.stories.tsx | 11 +++- src/pages/Map/MapExplorerPage.tsx | 5 +- 5 files changed, 115 insertions(+), 24 deletions(-) diff --git a/src/components/MapPage/SearchFullScreen/SearchFullScreen.tsx b/src/components/MapPage/SearchFullScreen/SearchFullScreen.tsx index 19d3e8ae..af5ab8bc 100644 --- a/src/components/MapPage/SearchFullScreen/SearchFullScreen.tsx +++ b/src/components/MapPage/SearchFullScreen/SearchFullScreen.tsx @@ -3,22 +3,22 @@ import { BackButton } from '@/components/Common/BackButton/BackButton'; import { IoIosSearch } from 'react-icons/io'; import { SearchHistoryList } from '../SearchHistoryList/SearchHistoryList'; -const searchHistory = [ - { place: '서울시 용산구 한강대로', date: '12.03.' }, - { place: '서울시 강남구 테헤란로', date: '12.03.' }, - { place: '서울시 송파구 올림픽로', date: '12.03.' } -]; - type SearchFullScreenProps = { isOpen: boolean; onClose: () => void; onChange: (event: React.ChangeEvent) => void; + recentSearches: { place: string; date: string }[]; + setRecentSearches: React.Dispatch< + React.SetStateAction<{ place: string; date: string }[]> + >; }; export const SearchFullScreen = ({ isOpen, onClose, - onChange + onChange, + recentSearches, + setRecentSearches }: SearchFullScreenProps) => { const inputRef = useRef(null); @@ -41,11 +41,25 @@ export const SearchFullScreen = ({ onBackClick(); } }; + const onSearchClick = () => { if (inputRef.current?.value.trim() !== '') { onBackClick(); } }; + + const onClearAll = () => { + localStorage.removeItem('recentSearches'); + setRecentSearches([]); + }; + + const onDelete = (index: number) => { + const updatedSearches = [...recentSearches]; + updatedSearches.splice(index, 1); + setRecentSearches(updatedSearches); + localStorage.setItem('recentSearches', JSON.stringify(updatedSearches)); + }; + return (
@@ -59,7 +73,7 @@ export const SearchFullScreen = ({ className="flex-1 outline-none" onChange={onChange} onKeyDown={onKeyDown} - > + />
최근 검색어 - 전체삭제 +
- {searchHistory.map((history, index) => ( - - ))} + {recentSearches.length > 0 ? ( + recentSearches.map((history, index) => ( + + )) + ) : ( +

+ 최근 검색어 내역이 없습니다. +

+ )}
); diff --git a/src/components/MapPage/SearchHistoryList/SearchHistoryList.tsx b/src/components/MapPage/SearchHistoryList/SearchHistoryList.tsx index 078c60b8..2f1a1bfb 100644 --- a/src/components/MapPage/SearchHistoryList/SearchHistoryList.tsx +++ b/src/components/MapPage/SearchHistoryList/SearchHistoryList.tsx @@ -2,15 +2,24 @@ import { LuMapPin } from 'react-icons/lu'; type SearchHistoryListProps = { place: string; date: string; + index: number; + onDelete: (index: number) => void; }; -export const SearchHistoryList = ({ place, date }: SearchHistoryListProps) => { +export const SearchHistoryList = ({ + place, + date, + index, + onDelete +}: SearchHistoryListProps) => { return (
{place} {date} - x + onDelete(index)}> + x +
); }; diff --git a/src/hooks/useNominatimSearch.ts b/src/hooks/useNominatimSearch.ts index eeeddce7..57376514 100644 --- a/src/hooks/useNominatimSearch.ts +++ b/src/hooks/useNominatimSearch.ts @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; +import { useEffect, useState } from 'react'; type SearchResult = { lat: string; @@ -8,6 +9,11 @@ type SearchResult = { display_name: string; }; +type SearchHistory = { + place: string; + date: string; +}; + const fetchNominatimSearch = async (query: string): Promise => { const res = await axios.get('https://nominatim.openstreetmap.org/search', { params: { @@ -20,11 +26,55 @@ const fetchNominatimSearch = async (query: string): Promise => { }; export default function useNominatimSearch(query: string) { - const { data, isLoading, error } = useQuery({ + const [recentSearches, setRecentSearches] = useState( + () => { + const savedSearches = localStorage.getItem('recentSearches'); + return savedSearches ? JSON.parse(savedSearches) : []; + } + ); + + const { data, isLoading, error } = useQuery({ queryKey: ['nominatimSearch', query], queryFn: () => fetchNominatimSearch(query), enabled: !!query }); - return { data, isLoading, error }; + useEffect(() => { + if (data && query) { + addRecentSearch(query); + } + }, [data, query]); + + const addRecentSearch = (newQuery: string) => { + const formattedDate = new Intl.DateTimeFormat('ko-KR', { + month: '2-digit', + day: '2-digit', + timeZone: 'Asia/Seoul' + }).format(new Date()); + + const [month, day] = formattedDate.split('/'); + const newSearch = { + place: newQuery, + date: `${month}.${day}.` + }; + + setRecentSearches((prev) => { + const updatedSearches = Array.from( + new Set([ + JSON.stringify(newSearch), + ...prev.map((s) => JSON.stringify(s)) + ]) + ) + .slice(0, 10) + .map((s) => JSON.parse(s)); + + localStorage.setItem( + 'recentSearches', + JSON.stringify(updatedSearches) + ); + return updatedSearches; + }); + }; + + return { data, isLoading, error, recentSearches, setRecentSearches }; } diff --git a/src/pages/Map/MapExplorerPage.stories.tsx b/src/pages/Map/MapExplorerPage.stories.tsx index ea4f3701..0adc0bff 100644 --- a/src/pages/Map/MapExplorerPage.stories.tsx +++ b/src/pages/Map/MapExplorerPage.stories.tsx @@ -1,6 +1,9 @@ import type { Meta, StoryObj } from '@storybook/react'; import { MapExplorerPage } from './MapExplorerPage'; import { MemoryRouter } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +const queryClient = new QueryClient(); const meta: Meta = { title: 'Pages/MapExplorerPage', @@ -8,9 +11,11 @@ const meta: Meta = { tags: ['autodocs'], decorators: [ (Story) => ( - - - + + + + + ) ], argTypes: { diff --git a/src/pages/Map/MapExplorerPage.tsx b/src/pages/Map/MapExplorerPage.tsx index dfa020d8..e64ddb86 100644 --- a/src/pages/Map/MapExplorerPage.tsx +++ b/src/pages/Map/MapExplorerPage.tsx @@ -15,7 +15,8 @@ export const MapExplorerPage = () => { const [isSearchFocused, setIsSearchFocused] = useState(false); const [isOpen, setIsOpen] = useState(false); const [query, setQuery] = useState(''); - const { error, data, isLoading } = useNominatimSearch(query); + const { error, data, isLoading, recentSearches, setRecentSearches } = + useNominatimSearch(query); const [searchedLocation, setSearchedLocation] = useState<{ lat: string; lon: string; @@ -63,6 +64,8 @@ export const MapExplorerPage = () => { isOpen={isOpen} onClose={onClose} onChange={onChange} + recentSearches={recentSearches} + setRecentSearches={setRecentSearches} /> {error &&

검색 오류: {error.message}

} {!isLoading && !error && data?.length === 0 && (