feat(Search): Admin Schedule 추천 검색어 연동 + 빈 상태 드롭다운 숨김
This commit is contained in:
parent
727b05f0f5
commit
9c2ff7458d
2 changed files with 78 additions and 79 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue