feat(Search): Mobile Schedule 추천 검색어 API 연동
This commit is contained in:
parent
9c2ff7458d
commit
2ad5341f9c
1 changed files with 47 additions and 33 deletions
|
|
@ -34,6 +34,8 @@ function MobileSchedule() {
|
||||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||||
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
|
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
|
||||||
const [originalSearchQuery, setOriginalSearchQuery] = useState(''); // 필터링용 원본 쿼리
|
const [originalSearchQuery, setOriginalSearchQuery] = useState(''); // 필터링용 원본 쿼리
|
||||||
|
const [suggestions, setSuggestions] = useState([]); // 추천 검색어 목록
|
||||||
|
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
|
||||||
|
|
||||||
// 검색 모드 진입 함수 (history 상태 추가)
|
// 검색 모드 진입 함수 (history 상태 추가)
|
||||||
const enterSearchMode = () => {
|
const enterSearchMode = () => {
|
||||||
|
|
@ -205,6 +207,34 @@ function MobileSchedule() {
|
||||||
};
|
};
|
||||||
}, [showSuggestions]);
|
}, [showSuggestions]);
|
||||||
|
|
||||||
|
// 검색어 자동완성 API 호출 (debounce 적용)
|
||||||
|
useEffect(() => {
|
||||||
|
// 검색어가 비어있으면 초기화
|
||||||
|
if (!originalSearchQuery || originalSearchQuery.trim().length === 0) {
|
||||||
|
setSuggestions([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// debounce: 200ms 후에 API 호출
|
||||||
|
const timeoutId = setTimeout(async () => {
|
||||||
|
setIsLoadingSuggestions(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/schedules/suggestions?q=${encodeURIComponent(originalSearchQuery)}&limit=10`);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setSuggestions(data.suggestions || []);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('추천 검색어 API 오류:', error);
|
||||||
|
setSuggestions([]);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingSuggestions(false);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}, [originalSearchQuery]);
|
||||||
|
|
||||||
// 카테고리 색상
|
// 카테고리 색상
|
||||||
const getCategoryColor = (categoryId) => {
|
const getCategoryColor = (categoryId) => {
|
||||||
const category = categories.find(c => c.id === categoryId);
|
const category = categories.find(c => c.id === categoryId);
|
||||||
|
|
@ -305,34 +335,29 @@ function MobileSchedule() {
|
||||||
}}
|
}}
|
||||||
onFocus={() => setShowSuggestions(true)}
|
onFocus={() => setShowSuggestions(true)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
// 필터링은 원본 쿼리 기준으로 유지
|
|
||||||
const dummySuggestions = ['성수기', '성수기 이채영', '이채영 먹방', 'NOW TOMORROW', '하얀 그리움', '콘서트', '월드투어'].filter(s =>
|
|
||||||
s.toLowerCase().includes(originalSearchQuery.toLowerCase())
|
|
||||||
).slice(0, 7);
|
|
||||||
|
|
||||||
if (e.key === 'ArrowDown') {
|
if (e.key === 'ArrowDown') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newIndex = selectedSuggestionIndex < dummySuggestions.length - 1
|
const newIndex = selectedSuggestionIndex < suggestions.length - 1
|
||||||
? selectedSuggestionIndex + 1
|
? selectedSuggestionIndex + 1
|
||||||
: 0;
|
: 0;
|
||||||
setSelectedSuggestionIndex(newIndex);
|
setSelectedSuggestionIndex(newIndex);
|
||||||
if (dummySuggestions[newIndex]) {
|
if (suggestions[newIndex]) {
|
||||||
setSearchInput(dummySuggestions[newIndex]);
|
setSearchInput(suggestions[newIndex]);
|
||||||
}
|
}
|
||||||
} else if (e.key === 'ArrowUp') {
|
} else if (e.key === 'ArrowUp') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newIndex = selectedSuggestionIndex > 0
|
const newIndex = selectedSuggestionIndex > 0
|
||||||
? selectedSuggestionIndex - 1
|
? selectedSuggestionIndex - 1
|
||||||
: dummySuggestions.length - 1;
|
: suggestions.length - 1;
|
||||||
setSelectedSuggestionIndex(newIndex);
|
setSelectedSuggestionIndex(newIndex);
|
||||||
if (dummySuggestions[newIndex]) {
|
if (suggestions[newIndex]) {
|
||||||
setSearchInput(dummySuggestions[newIndex]);
|
setSearchInput(suggestions[newIndex]);
|
||||||
}
|
}
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (selectedSuggestionIndex >= 0 && dummySuggestions[selectedSuggestionIndex]) {
|
if (selectedSuggestionIndex >= 0 && suggestions[selectedSuggestionIndex]) {
|
||||||
setSearchInput(dummySuggestions[selectedSuggestionIndex]);
|
setSearchInput(suggestions[selectedSuggestionIndex]);
|
||||||
setSearchTerm(dummySuggestions[selectedSuggestionIndex]);
|
setSearchTerm(suggestions[selectedSuggestionIndex]);
|
||||||
} else if (searchInput.trim()) {
|
} else if (searchInput.trim()) {
|
||||||
setSearchTerm(searchInput);
|
setSearchTerm(searchInput);
|
||||||
}
|
}
|
||||||
|
|
@ -578,23 +603,12 @@ function MobileSchedule() {
|
||||||
!searchTerm ? (
|
!searchTerm ? (
|
||||||
// 검색어 입력 전 - 추천 검색어 리스트 표시 (유튜브 스타일)
|
// 검색어 입력 전 - 추천 검색어 리스트 표시 (유튜브 스타일)
|
||||||
<div className="space-y-0">
|
<div className="space-y-0">
|
||||||
{(() => {
|
{suggestions.length === 0 ? (
|
||||||
const dummySuggestions = ['성수기', '성수기 이채영', '이채영 먹방', 'NOW TOMORROW', '하얀 그리움', '콘서트', '월드투어'];
|
|
||||||
|
|
||||||
// 입력값이 있으면 필터링
|
|
||||||
const filtered = originalSearchQuery.length > 0
|
|
||||||
? dummySuggestions.filter(s => s.toLowerCase().includes(originalSearchQuery.toLowerCase()))
|
|
||||||
: dummySuggestions;
|
|
||||||
|
|
||||||
if (filtered.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="text-center py-8 text-gray-400">
|
<div className="text-center py-8 text-gray-400">
|
||||||
추천 검색어가 없습니다
|
검색어를 입력하세요
|
||||||
</div>
|
</div>
|
||||||
);
|
) : (
|
||||||
}
|
suggestions.map((suggestion, index) => (
|
||||||
|
|
||||||
return filtered.map((suggestion, index) => (
|
|
||||||
<button
|
<button
|
||||||
key={suggestion}
|
key={suggestion}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -612,8 +626,8 @@ function MobileSchedule() {
|
||||||
<Search size={18} className="text-gray-400 shrink-0" />
|
<Search size={18} className="text-gray-400 shrink-0" />
|
||||||
<span className="text-[15px] text-gray-700 flex-1">{suggestion}</span>
|
<span className="text-[15px] text-gray-700 flex-1">{suggestion}</span>
|
||||||
</button>
|
</button>
|
||||||
));
|
))
|
||||||
})()}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : searchLoading ? (
|
) : searchLoading ? (
|
||||||
<div className="flex justify-center py-8">
|
<div className="flex justify-center py-8">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue