Skip to content

Commit

Permalink
209 feat 지도 편지 페이지 최근검색어 기능 개발 (#215)
Browse files Browse the repository at this point in the history
* feat: 스토리북 QueryClientProvider 설정

* feat: 지도 편지 페이지 최근검색어 기능 개발 완료
  • Loading branch information
mmjjaa authored Dec 4, 2024
1 parent ac107c7 commit a6e8159
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 24 deletions.
56 changes: 40 additions & 16 deletions src/components/MapPage/SearchFullScreen/SearchFullScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLInputElement>) => 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<HTMLInputElement | null>(null);

Expand All @@ -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 (
<div className="mx-auto border min-w-[375px] max-w-[475px] h-[1vh]">
<div className="flex items-center pl-3 pr-4 justify-between w-full border-b h-12">
Expand All @@ -59,24 +73,34 @@ export const SearchFullScreen = ({
className="flex-1 outline-none"
onChange={onChange}
onKeyDown={onKeyDown}
></input>
/>
<IoIosSearch
className="w-6 h-12 cursor-pointer"
onClick={onSearchClick}
/>
</div>
<div className="flex justify-between text-gray-400 py-2 px-4">
<span>최근 검색어</span>
<span>전체삭제</span>
<button className="text-sm text-gray-400" onClick={onClearAll}>
전체삭제
</button>
</div>
<div className="py-2 px-4">
{searchHistory.map((history, index) => (
<SearchHistoryList
key={index}
place={history.place}
date={history.date}
/>
))}
{recentSearches.length > 0 ? (
recentSearches.map((history, index) => (
<SearchHistoryList
key={index}
place={history.place}
date={history.date}
index={index}
onDelete={onDelete}
/>
))
) : (
<p className="text-gray-500 flex-center text-sm mt-16">
최근 검색어 내역이 없습니다.
</p>
)}
</div>
</div>
);
Expand Down
13 changes: 11 additions & 2 deletions src/components/MapPage/SearchHistoryList/SearchHistoryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="flex items-center py-2 justify-between w-full text-gray-400">
<LuMapPin />
<span className="flex-1">{place}</span>
<span className="mr-2">{date}</span>
<span className="cursor-pointer">x</span>
<span className="cursor-pointer" onClick={() => onDelete(index)}>
x
</span>
</div>
);
};
54 changes: 52 additions & 2 deletions src/hooks/useNominatimSearch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import { useEffect, useState } from 'react';

type SearchResult = {
lat: string;
Expand All @@ -8,6 +9,11 @@ type SearchResult = {
display_name: string;
};

type SearchHistory = {
place: string;
date: string;
};

const fetchNominatimSearch = async (query: string): Promise<SearchResult[]> => {
const res = await axios.get('https://nominatim.openstreetmap.org/search', {
params: {
Expand All @@ -20,11 +26,55 @@ const fetchNominatimSearch = async (query: string): Promise<SearchResult[]> => {
};

export default function useNominatimSearch(query: string) {
const { data, isLoading, error } = useQuery({
const [recentSearches, setRecentSearches] = useState<SearchHistory[]>(
() => {
const savedSearches = localStorage.getItem('recentSearches');
return savedSearches ? JSON.parse(savedSearches) : [];
}
);

const { data, isLoading, error } = useQuery<SearchResult[]>({
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 };
}
11 changes: 8 additions & 3 deletions src/pages/Map/MapExplorerPage.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
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<typeof MapExplorerPage> = {
title: 'Pages/MapExplorerPage',
component: MapExplorerPage,
tags: ['autodocs'],
decorators: [
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<Story />
</MemoryRouter>
</QueryClientProvider>
)
],
argTypes: {
Expand Down
5 changes: 4 additions & 1 deletion src/pages/Map/MapExplorerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,6 +64,8 @@ export const MapExplorerPage = () => {
isOpen={isOpen}
onClose={onClose}
onChange={onChange}
recentSearches={recentSearches}
setRecentSearches={setRecentSearches}
/>
{error && <p>검색 오류: {error.message}</p>}
{!isLoading && !error && data?.length === 0 && (
Expand Down

0 comments on commit a6e8159

Please sign in to comment.