feat(Search): Admin Schedule 추천 검색어 연동 + 빈 상태 드롭다운 숨김

This commit is contained in:
caadiq 2026-01-11 21:39:23 +09:00
parent 727b05f0f5
commit 9c2ff7458d
2 changed files with 78 additions and 79 deletions

View file

@ -214,6 +214,34 @@ function AdminSchedule() {
prevInViewRef.current = inView;
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage, isSearchMode, searchTerm]);
// 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]);
// selectedDate
useEffect(() => {
if (!selectedDate) {
@ -1005,33 +1033,28 @@ function AdminSchedule() {
}}
onFocus={() => setShowSuggestions(true)}
onKeyDown={(e) => {
//
const dummySuggestions = ['성수기', '성수기 이채영', '이채영 먹방', 'NOW TOMORROW', '하얀 그리움', '콘서트', '월드투어'].filter(s =>
s.toLowerCase().includes(originalSearchQuery.toLowerCase())
).slice(0, 7);
if (e.key === 'ArrowDown') {
e.preventDefault();
const newIndex = selectedSuggestionIndex < dummySuggestions.length - 1
const newIndex = selectedSuggestionIndex < suggestions.length - 1
? selectedSuggestionIndex + 1
: 0;
setSelectedSuggestionIndex(newIndex);
if (dummySuggestions[newIndex]) {
setSearchInput(dummySuggestions[newIndex]);
if (suggestions[newIndex]) {
setSearchInput(suggestions[newIndex]);
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
const newIndex = selectedSuggestionIndex > 0
? selectedSuggestionIndex - 1
: dummySuggestions.length - 1;
: suggestions.length - 1;
setSelectedSuggestionIndex(newIndex);
if (dummySuggestions[newIndex]) {
setSearchInput(dummySuggestions[newIndex]);
if (suggestions[newIndex]) {
setSearchInput(suggestions[newIndex]);
}
} else if (e.key === 'Enter') {
if (selectedSuggestionIndex >= 0 && dummySuggestions[selectedSuggestionIndex]) {
setSearchInput(dummySuggestions[selectedSuggestionIndex]);
setSearchTerm(dummySuggestions[selectedSuggestionIndex]);
if (selectedSuggestionIndex >= 0 && suggestions[selectedSuggestionIndex]) {
setSearchInput(suggestions[selectedSuggestionIndex]);
setSearchTerm(suggestions[selectedSuggestionIndex]);
} else if (searchInput.trim()) {
setSearchTerm(searchInput);
}
@ -1065,41 +1088,27 @@ function AdminSchedule() {
</div>
{/* 검색어 추천 드롭다운 */}
{showSuggestions && originalSearchQuery.length > 0 && (
{showSuggestions && !isLoadingSuggestions && suggestions.length > 0 && (
<div className="absolute top-full left-0 right-8 mt-2 bg-white rounded-xl shadow-lg border border-gray-200 py-1 z-50 overflow-hidden">
{(() => {
const dummySuggestions = ['성수기', '성수기 이채영', '이채영 먹방', 'NOW TOMORROW', '하얀 그리움', '콘서트', '월드투어'].filter(s =>
s.toLowerCase().includes(originalSearchQuery.toLowerCase())
).slice(0, 7);
if (dummySuggestions.length === 0) {
return (
<div className="px-4 py-3 text-sm text-gray-400 text-center">
추천 검색어가 없습니다
</div>
);
}
return dummySuggestions.map((suggestion, index) => (
<button
key={suggestion}
onClick={() => {
setSearchInput(suggestion);
setSearchTerm(suggestion);
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
}}
className={`w-full px-4 py-2 text-left text-sm flex items-center gap-3 transition-colors ${
index === selectedSuggestionIndex
? 'bg-primary/10 text-primary'
: 'text-gray-700 hover:bg-gray-50'
}`}
>
<Search size={14} className="text-gray-400 shrink-0" />
<span>{suggestion}</span>
</button>
));
})()}
{suggestions.map((suggestion, index) => (
<button
key={suggestion}
onClick={() => {
setSearchInput(suggestion);
setSearchTerm(suggestion);
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
}}
className={`w-full px-4 py-2 text-left text-sm flex items-center gap-3 transition-colors ${
index === selectedSuggestionIndex
? 'bg-primary/10 text-primary'
: 'text-gray-700 hover:bg-gray-50'
}`}
>
<Search size={14} className="text-gray-400 shrink-0" />
<span>{suggestion}</span>
</button>
))}
</div>
)}
</div>

View file

@ -854,38 +854,28 @@ function Schedule() {
</div>
{/* 검색어 추천 드롭다운 */}
{showSuggestions && originalSearchQuery.length > 0 && (
{showSuggestions && !isLoadingSuggestions && suggestions.length > 0 && (
<div className="absolute top-full mt-2 bg-white rounded-xl shadow-lg border border-gray-200 py-1 z-50 overflow-hidden" style={{ left: '44px', right: '66px' }}>
{isLoadingSuggestions ? (
<div className="px-4 py-3 text-gray-400 text-sm text-center">
검색 ...
</div>
) : suggestions.length === 0 ? (
<div className="px-4 py-3 text-gray-400 text-sm text-center">
추천 검색어가 없습니다
</div>
) : (
suggestions.map((suggestion, index) => (
<button
key={index}
onClick={() => {
setSearchInput(suggestion);
setSearchTerm(suggestion);
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
}}
onMouseEnter={() => setSelectedSuggestionIndex(index)}
className={`w-full px-4 py-2.5 text-left flex items-center gap-3 transition-colors ${
selectedSuggestionIndex === index
? 'bg-primary/10 text-primary'
: 'hover:bg-gray-50 text-gray-700'
}`}
>
<Search size={15} className={selectedSuggestionIndex === index ? 'text-primary' : 'text-gray-400'} />
<span className="text-sm">{suggestion}</span>
</button>
))
)}
{suggestions.map((suggestion, index) => (
<button
key={index}
onClick={() => {
setSearchInput(suggestion);
setSearchTerm(suggestion);
setShowSuggestions(false);
setSelectedSuggestionIndex(-1);
}}
onMouseEnter={() => setSelectedSuggestionIndex(index)}
className={`w-full px-4 py-2.5 text-left flex items-center gap-3 transition-colors ${
selectedSuggestionIndex === index
? 'bg-primary/10 text-primary'
: 'hover:bg-gray-50 text-gray-700'
}`}
>
<Search size={15} className={selectedSuggestionIndex === index ? 'text-primary' : 'text-gray-400'} />
<span className="text-sm">{suggestion}</span>
</button>
))}
</div>
)}
</div>