feat(Search): Mobile Schedule 추천 검색어 API 연동

This commit is contained in:
caadiq 2026-01-11 21:44:55 +09:00
parent 9c2ff7458d
commit 2ad5341f9c

View file

@ -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', '하얀 그리움', '콘서트', '월드투어']; <div className="text-center py-8 text-gray-400">
검색어를 입력하세요
// </div>
const filtered = originalSearchQuery.length > 0 ) : (
? dummySuggestions.filter(s => s.toLowerCase().includes(originalSearchQuery.toLowerCase())) suggestions.map((suggestion, index) => (
: dummySuggestions;
if (filtered.length === 0) {
return (
<div className="text-center py-8 text-gray-400">
추천 검색어가 없습니다
</div>
);
}
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">