Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 37e855c

Browse files
authored
♻️ Refactor: 페이지네이션 구현 및 검색창 개선
* 🐛 Bug: SearchBar 컴포넌트 검색타입 반영하지 못하는 오류 수정 * ✨ Feat: 검색결과 페이지네이션 구현 * ✨ Feat: 검색결과 페이지네이션 구현 * 💄 Style: 검색바 로딩 시 화면 변하지 않도록 수정 * 💄 Style: NoImage 컴포넌트 디자인 변경 * 📦️ Chore: 검색결과 페이지 주소 파라미터 추가 및 에러 메시지 출력 수정 * ♻️ Refactor: 백엔드 API 변경 반영 * ♻️ Refactor: 백엔드 api 엔드포인트 변경내용 반영.
1 parent dbc3706 commit 37e855c

File tree

5 files changed

+148
-73
lines changed

5 files changed

+148
-73
lines changed

src/app/drugs/[id]/page.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ export default function DrugDetailPage() {
1515
const [error, setError] = useState(null);
1616

1717
useEffect(() => {
18-
// API 개발이 완료되면 아래 주석을 해제하고 mock 데이터 대신 실제 API를 사용하면 됩니다.
1918

2019
const fetchDrugDetail = async () => {
2120
try {
2221
setLoading(true);
23-
const response = await fetch(`/api/api/drugs/search/detail/${drugId}`);
22+
const response = await fetch(`/api/drugs/search/detail/${drugId}`);
2423
if (!response.ok) {
2524
throw new Error('약품 정보를 불러오는 데 실패했습니다.');
2625
}
@@ -36,16 +35,6 @@ export default function DrugDetailPage() {
3635
};
3736

3837
fetchDrugDetail();
39-
40-
41-
// Mock 데이터 사용
42-
// setTimeout(() => {
43-
// setDrug({
44-
// ...mockDrugData,
45-
// item_seq: drugId, // URL의 ID 반영
46-
// });
47-
// setLoading(false);
48-
// }, 500); // 로딩 효과를 위한 지연 시간
4938
}, [drugId]);
5039

5140
if (loading) {

src/app/page.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,38 @@ import Image from "next/image";
55
import Header from '../components/Header';
66
import Footer from '../components/Footer';
77

8+
// 스켈레톤 UI 컴포넌트
9+
const SearchBarSkeleton = () => (
10+
<div className="w-full">
11+
{/* 검색 모드 선택 탭을 위한 스켈레톤 */}
12+
<div className="flex mb-4 border-b border-gray-200">
13+
<div className="px-8 py-3 w-20 bg-gray-100 animate-pulse rounded-md"></div>
14+
<div className="px-8 py-3 w-20 bg-gray-100 animate-pulse rounded-md ml-4"></div>
15+
</div>
16+
17+
{/* 검색 입력 필드를 위한 스켈레톤 */}
18+
<div className="flex gap-4">
19+
<div className="flex-1 relative">
20+
<div className="w-full h-[52px] rounded-lg bg-gray-100 animate-pulse"></div>
21+
</div>
22+
23+
{/* 드롭다운 버튼을 위한 스켈레톤 */}
24+
<div className="w-[100px] h-[52px] bg-gray-100 animate-pulse rounded-lg"></div>
25+
</div>
26+
</div>
27+
);
28+
829
const SearchBar = dynamic(() => import('../components/SearchBar'), {
930
ssr: false,
10-
loading: () => <div>검색창 로딩 중...</div>,
31+
loading: () => <SearchBarSkeleton />,
1132
});
1233

13-
1434
const MAX_RECENT_SEARCHES = 5; // 최대 저장할 최근 검색어 수
1535

1636
const displayTypes = {
1737
symptom: '증상',
1838
company: '제조사',
19-
medicine: '약품명',
39+
name: '약품명',
2040
natural: '자연어'
2141
};
2242

src/components/NoImage.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ const NoImage = ({ className = "" }) => {
4242
/> */}
4343

4444
{/* X 표시 - 우측 하단으로 이동 및 크기 확대 */}
45-
<path
45+
{/* <path
4646
strokeLinecap="round"
4747
strokeLinejoin="round"
4848
strokeWidth="2"
4949
d="M19 15L13 21M13 15l6 6"
5050
stroke="#EF4444"
51-
/>
51+
/> */}
5252
</svg>
53-
<p className="mt-1 text-xs text-gray-500">약품 이미지 없음</p>
53+
<p className="mt-1 text-xs text-gray-500">약품 이미지 준비 중</p>
5454
</div>
5555
</div>
5656
);

src/components/SearchBar.js

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,10 @@ const SearchIcon = ({ color = '#2BA89C', onClick }) => (
3737
</button>
3838
);
3939

40-
const SearchBar = ({ initialQuery = '', showTabs = true, initialMode = 'keyword', initialSearchType = 'symptom'}) => {
40+
const SearchBar = ({ initialQuery = '', showTabs = true, initialMode = 'keyword', initialType = 'symptom'}) => {
4141
const router = useRouter();
42-
const searchParams = useSearchParams();
4342
const [searchMode, setSearchMode] = useState(initialMode);
44-
const urlType = searchParams.get('type');
45-
const [searchType, setSearchType] = useState(urlType || initialSearchType);
43+
const [searchType, setSearchType] = useState(initialType);
4644
const [searchQuery, setSearchQuery] = useState(initialQuery);
4745
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
4846
const [suggestions, setSuggestions] = useState([]);
@@ -96,7 +94,7 @@ const updateCache = (prevCache, key, value) => {
9694
try {
9795
setIsLoading(true);
9896

99-
const url = `/api/api/drugs/autocomplete/${type}?q=${encodeURIComponent(query)}`;
97+
const url = `/api/drugs/autocomplete/${type}?q=${encodeURIComponent(query)}`;
10098

10199
console.log('자동완성 요청 URL:', url);
102100

@@ -110,7 +108,7 @@ const updateCache = (prevCache, key, value) => {
110108
console.log('자동완성 응답:', response);
111109

112110
if (!response.ok) {
113-
throw new Error('자동완성 데이터를 가져오는데 실패했습니다.');
111+
throw new Error(response.message);
114112
}
115113

116114
const data = await response.json();
@@ -132,6 +130,7 @@ const updateCache = (prevCache, key, value) => {
132130
}
133131
};
134132

133+
// 자동완성 결과 업데이트
135134
useEffect(() => {
136135
if (!isFocused) {
137136
setSuggestions([]);
@@ -176,17 +175,17 @@ const updateCache = (prevCache, key, value) => {
176175
};
177176

178177
// 최근 검색어 저장 함수
179-
const saveRecentSearch = (query, type, mode) => {
178+
const saveRecentSearch = (query, mode, type) => {
180179
try {
181180
const savedSearches = sessionStorage.getItem('recentSearches');
182181
const searches = savedSearches ? JSON.parse(savedSearches) : [];
183182

184183
const newSearch = {
185184
query: query.trim(),
186-
type: type,
187-
mode: mode
185+
mode: mode,
186+
type: type
188187
};
189-
188+
190189
const filteredSearches = searches.filter(item => item.query !== newSearch.query);
191190
const updatedSearches = [newSearch, ...filteredSearches].slice(0, 5);
192191

@@ -196,31 +195,33 @@ const updateCache = (prevCache, key, value) => {
196195
}
197196
};
198197

198+
// 검색 모드(키워드,자연어) 감지하여 모드별 검색 결과창 라우팅
199199
const handleSearch = (e) => {
200200
e.preventDefault();
201201
if (!searchQuery.trim()) return;
202202

203203
if (searchMode === 'keyword') {
204204
switch (searchType) {
205205
case 'symptom':
206-
saveRecentSearch(searchQuery, searchType, searchMode);
207-
router.push(`/search/symptom?q=${encodeURIComponent(searchQuery)}&mode=${searchMode}`);
206+
saveRecentSearch(searchQuery, searchMode, searchType);
207+
router.push(`/search/symptom?q=${encodeURIComponent(searchQuery)}&mode=${searchMode}&type=${searchType}`);
208208
break;
209209
case 'company':
210210
// 추후 구현
211211
break;
212212
case 'name':
213-
saveRecentSearch(searchQuery, searchType, searchMode);
214-
router.push(`/search/name?q=${encodeURIComponent(searchQuery)}&mode=${searchMode}`);
213+
saveRecentSearch(searchQuery, searchMode, searchType);
214+
router.push(`/search/name?q=${encodeURIComponent(searchQuery)}&mode=${searchMode}&type=${searchType}`);
215215
break;
216216
}
217217
} else {
218218
// 자연어 검색 처리
219-
saveRecentSearch(searchQuery, 'natural', searchMode);
220-
router.push(`/search?q=${encodeURIComponent(searchQuery)}&mode=${searchMode}`);
219+
saveRecentSearch(searchQuery, searchMode, 'natural');
220+
router.push(`/search?q=${encodeURIComponent(searchQuery)}&mode=${searchMode}&type=natural`);
221221
}
222222
};
223223

224+
224225
const handleInputChange = (e) => {
225226
const value = e.target.value;
226227
if (searchMode === 'natural' && value.length > 20) {
@@ -230,6 +231,7 @@ const updateCache = (prevCache, key, value) => {
230231
setSelectedSuggestion(-1);
231232
};
232233

234+
233235
const handleKeyDown = (e) => {
234236
if (searchMode === 'natural' && e.key === 'Enter') {
235237
e.preventDefault();
@@ -253,8 +255,8 @@ const updateCache = (prevCache, key, value) => {
253255
case 'Enter':
254256
e.preventDefault();
255257
if (selectedSuggestion >= 0) {
256-
setSearchQuery(suggestions[selectedSuggestion].text);
257-
setSuggestions([]);
258+
const selectedItem = suggestions[selectedSuggestion];
259+
handleSuggestionClick(selectedItem);
258260
} else {
259261
handleSearch(e);
260262
}
@@ -284,7 +286,7 @@ const updateCache = (prevCache, key, value) => {
284286
const handleSuggestionClick = (suggestion) => {
285287
setSearchQuery(suggestion.text);
286288
setSuggestions([]);
287-
saveRecentSearch(suggestion.text, searchType, 'keyword');
289+
saveRecentSearch(suggestion.text, 'keyword', searchType);
288290
switch (searchType) {
289291
case 'symptom':
290292
router.push(`/search/symptom?q=${encodeURIComponent(suggestion.text)}&mode=keyword&type=${searchType}`);
@@ -423,7 +425,7 @@ const updateCache = (prevCache, key, value) => {
423425
} ${type === 'name' ? 'rounded-b-lg' : ''} ${
424426
type === 'symptom' ? 'rounded-t-lg' : ''
425427
}`}
426-
>
428+
>
427429
{label}
428430
</button>
429431
))}

0 commit comments

Comments
 (0)