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

Commit 7ac9dfd

Browse files
authored
✨ Feat: 자연어 검색 백엔드 연동
* ✨ Feat: 증상 자연어 검색 백엔드 연동 * ✨ Feat: 자연어 검색 백엔드 연동
1 parent 643ebb4 commit 7ac9dfd

File tree

2 files changed

+260
-1
lines changed

2 files changed

+260
-1
lines changed

src/app/search/page.js

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
'use client';
2+
3+
import { useSearchParams } from 'next/navigation';
4+
import { useState, useEffect, Suspense } from 'react';
5+
import Header from '../../components/Header';
6+
import Footer from '../../components/Footer';
7+
import NoImage from '../../components/NoImage';
8+
import SearchBar from '../../components/SearchBar';
9+
10+
// SearchParams를 사용하는 컴포넌트를 분리
11+
const SearchResults = () => {
12+
const searchParams = useSearchParams();
13+
const query = searchParams.get('q');
14+
15+
// 페이지네이션 관련 상태
16+
const [currentPage, setCurrentPage] = useState(1);
17+
const [totalResults, setTotalResults] = useState(0); // 전체 검색 결과 수
18+
const itemsPerPage = 10; // 페이지당 항목 수
19+
20+
// 서버에서 받아올 실제 검색 결과
21+
const [fetchedResults, setFetchedResults] = useState([]);
22+
const [isLoading, setIsLoading] = useState(false);
23+
const [error, setError] = useState(null);
24+
25+
// query 또는 currentPage가 바뀔 때마다 서버 요청
26+
useEffect(() => {
27+
if (!query) {
28+
setFetchedResults([]);
29+
return;
30+
}
31+
setIsLoading(true);
32+
setError(null);
33+
const apiPage = currentPage - 1; // API에 0부터 시작하는 페이지 인덱스 전달
34+
const url = `/api/api/drugs/search`;
35+
const body = {
36+
query: query,
37+
page: apiPage,
38+
size: itemsPerPage,
39+
};
40+
41+
fetch(
42+
url, {
43+
method: 'POST',
44+
headers: {'Content-Type': 'application/json'},
45+
body: JSON.stringify(body),
46+
})
47+
.then(res => {
48+
if (!res.ok) throw new Error('서버 에러');
49+
return res.json();
50+
})
51+
.then(data => {
52+
const list = data.data;
53+
setFetchedResults(list);
54+
setTotalResults(list.length);
55+
})
56+
.catch(err => setError(err.message))
57+
.finally(() => setIsLoading(false));
58+
}, [query, currentPage]);
59+
60+
// const [selectedType, setSelectedType] = useState('all');
61+
// const [sortBy, setSortBy] = useState('name');
62+
// const [showTypeDropdown, setShowTypeDropdown] = useState(false);
63+
// const [showSortDropdown, setShowSortDropdown] = useState(false);
64+
65+
66+
67+
// // 임시 검색 결과 데이터
68+
// const mockResults = [
69+
// {
70+
// id: 1,
71+
// name: "타이레놀",
72+
// category: "일반 의약품",
73+
// company: "(주)한국얀센 / Janssen Korea",
74+
// effect: "아세트아미노펜 과립",
75+
// symptoms: ["감기로 인한 발열 및 동통(통증), 두통, 신경통, 근육통, 월경통, 염좌통(삔 통증)"],
76+
// image: null
77+
// },
78+
// {
79+
// id: 2,
80+
// name: "부루펜",
81+
// category: "일반 의약품",
82+
// company: "삼일제약(주) / Samil",
83+
// effect: "이부프로펜",
84+
// symptoms: ["류마티양 관절염, 연소성 류마티양 관절염, 골관절염(퇴행성 관절질환), 감기로 인한 발열 및 동통, 요통, 월경곤란증, 수술후 동통"],
85+
// image: null
86+
// },
87+
// {
88+
// id: 3,
89+
// name: "트라펜정",
90+
// category: "전문 의약품",
91+
// company: "명문제약(주) / Myungmoon Pharm",
92+
// effect: "트라마돌염산염,아세트아미노펜",
93+
// symptoms: ["중등도-중증의 급ㆍ만성 통증"],
94+
// image: null
95+
// },
96+
// ];
97+
98+
// // 검색어에 따른 결과 필터링 (임시로 "두통" 검색어만 결과 표시)
99+
// const searchResults = query?.toLowerCase() === "두통" ? mockResults : [];
100+
101+
// // 선택된 타입에 따라 결과 필터링
102+
// const filteredResults = selectedType === 'all'
103+
// ? fetchedResults
104+
// : fetchedResults.filter(medicine => {
105+
// if (selectedType === 'general') return medicine.category === "일반 의약품";
106+
// if (selectedType === 'prescription') return medicine.category === "전문 의약품";
107+
// return true;
108+
// });
109+
110+
// // 정렬 기준에 따라 결과 정렬
111+
// const sortedResults = [...filteredResults].sort((a, b) => {
112+
// switch (sortBy) {
113+
// case 'name':
114+
// return a.name.localeCompare(b.name, 'ko');
115+
// case 'effect':
116+
// return a.effect.localeCompare(b.effect, 'ko');
117+
// case 'company':
118+
// return a.company.localeCompare(b.company, 'ko');
119+
// default:
120+
// return 0;
121+
// }
122+
// });
123+
124+
// 검색 결과 수 업데이트
125+
// useEffect(() => {
126+
// setTotalResults(sortedResults.length);
127+
// }, [sortedResults.length]);
128+
129+
// // 드롭다운 외부 클릭 시 닫기
130+
// const handleClickOutside = () => {
131+
// setShowTypeDropdown(false);
132+
// setShowSortDropdown(false);
133+
// };
134+
135+
// const getTypeLabel = () => {
136+
// switch (selectedType) {
137+
// case 'all': return '전체';
138+
// case 'general': return '일반 의약품';
139+
// case 'prescription': return '전문 의약품';
140+
// default: return '전체';
141+
// }
142+
// };
143+
144+
// const getSortLabel = () => {
145+
// switch (sortBy) {
146+
// case 'name': return '제품명';
147+
// case 'effect': return '성분명';
148+
// case 'company': return '제약회사';
149+
// default: return '제품명';
150+
// }
151+
// };
152+
153+
// // 현재 필터 상태 텍스트 생성
154+
// const getFilterStatusText = () => {
155+
// const typeText = selectedType === 'all' ? '' : `${getTypeLabel()} · `;
156+
// const sortText = `${getSortLabel()} 순`;
157+
// return `${typeText}${sortText}`;
158+
// };
159+
160+
return (
161+
<div className="min-h-screen flex flex-col bg-gray-50">
162+
<Header />
163+
<main className="flex-1 w-full mt-[64px]">
164+
<div className="max-w-7xl mx-auto w-full px-4">
165+
{/* 검색창 영역 */}
166+
<div className="sticky top-[64px] bg-gray-50 z-10">
167+
<SearchBar
168+
initialQuery={query || ''}
169+
showTabs={true}
170+
initialMode="keyword"
171+
/>
172+
</div>
173+
174+
{/* 검색 결과 목록 */}
175+
<div className="space-y-4 mt-6">
176+
{isLoading ? (
177+
<div className="text-center py-16">로딩 중...</div>
178+
) : error ? (
179+
<div className="text-center py-16 text-red-500">에러: {error}</div>
180+
) : fetchedResults.length > 0 ? (
181+
fetchedResults.map((medicine) => (
182+
<div
183+
key={medicine.drugId}
184+
className="bg-white rounded-lg shadow-sm p-4 border border-transparent hover:shadow-md hover:border-[#2BA89C] transition"
185+
// className="bg-white rounded-lg shadow-sm p-2 hover:shadow-md transition-shadow"
186+
>
187+
<div className="flex divide-x divide-gray-200">
188+
{/* <div className="flex items-start gap-4"> */}
189+
{/* <div className="flex gap-4"> */}
190+
<div className="w-48 h-48 flex-shrink-0 pr-4">
191+
{medicine.imageUrl ? (
192+
<img
193+
src={medicine.imageUrl}
194+
alt={medicine.drugName}
195+
className="w-full h-full rounded-lg object-contain"
196+
/>
197+
) : (
198+
<NoImage />
199+
)}
200+
</div>
201+
202+
{/* 정보영역 */}
203+
<div className="flex-1 mt-11 pl-4 space-y-2 leading-relaxed">
204+
{/* <div className="flex-1 mt-20 space-y-3 leading-relaxed"> */}
205+
<p className="text-base text-gray-600">명칭: {medicine.drugName}</p>
206+
<p className="text-base text-gray-600">제약회사: {medicine.company}</p>
207+
<p className="text-base text-gray-600">
208+
효능: {medicine.efficacy.join(', ')}
209+
</p>
210+
</div>
211+
</div>
212+
</div>
213+
))
214+
) : (
215+
<div className="text-center py-16">
216+
<p className="text-gray-500 mb-2">검색 결과가 없습니다.</p>
217+
<p className="text-sm text-gray-400">다른 검색어로 다시 시도해 보세요.</p>
218+
</div>
219+
)}
220+
</div>
221+
222+
223+
224+
225+
{/* 페이지네이션 */}
226+
{fetchedResults.length > 0 && (
227+
<div className="flex justify-center mt-8 gap-2">
228+
{[...Array(Math.ceil(totalResults / itemsPerPage)).keys()].map(i => (
229+
<button
230+
key={i + 1}
231+
onClick={() => setCurrentPage(i + 1)}
232+
className={`px-3 py-1 rounded ${
233+
i + 1 === currentPage
234+
? 'bg-[#2BA89C] text-white'
235+
: 'bg-white text-gray-600 hover:bg-gray-100'
236+
}`}
237+
>
238+
{i + 1}
239+
</button>
240+
))}
241+
</div>
242+
)}
243+
</div>
244+
</main>
245+
<Footer />
246+
</div>
247+
);
248+
};
249+
250+
// 메인 페이지 컴포넌트
251+
const SymptomSearchResults = () => {
252+
return (
253+
<Suspense fallback={<div>Loading...</div>}>
254+
<SearchResults />
255+
</Suspense>
256+
);
257+
};
258+
259+
export default SymptomSearchResults;

src/components/SearchBar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ const updateCache = (prevCache, key, value) => {
305305
} else {
306306
// 자연어 검색 처리
307307
saveRecentSearch(searchQuery, 'natural', searchMode);
308-
// router.push(`/search/symptom?q=${encodeURIComponent(searchQuery)}&mode=natural`);
308+
router.push(`/search?q=${encodeURIComponent(searchQuery)}`);
309309
}
310310
};
311311

0 commit comments

Comments
 (0)