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;
|
prevInViewRef.current = inView;
|
||||||
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage, isSearchMode, searchTerm]);
|
}, [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가 없으면 오늘 날짜로 초기화
|
// selectedDate가 없으면 오늘 날짜로 초기화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedDate) {
|
if (!selectedDate) {
|
||||||
|
|
@ -1005,33 +1033,28 @@ function AdminSchedule() {
|
||||||
}}
|
}}
|
||||||
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') {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
@ -1065,41 +1088,27 @@ function AdminSchedule() {
|
||||||
</div>
|
</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">
|
<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">
|
||||||
{(() => {
|
{suggestions.map((suggestion, index) => (
|
||||||
const dummySuggestions = ['성수기', '성수기 이채영', '이채영 먹방', 'NOW TOMORROW', '하얀 그리움', '콘서트', '월드투어'].filter(s =>
|
<button
|
||||||
s.toLowerCase().includes(originalSearchQuery.toLowerCase())
|
key={suggestion}
|
||||||
).slice(0, 7);
|
onClick={() => {
|
||||||
|
setSearchInput(suggestion);
|
||||||
if (dummySuggestions.length === 0) {
|
setSearchTerm(suggestion);
|
||||||
return (
|
setShowSuggestions(false);
|
||||||
<div className="px-4 py-3 text-sm text-gray-400 text-center">
|
setSelectedSuggestionIndex(-1);
|
||||||
추천 검색어가 없습니다
|
}}
|
||||||
</div>
|
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'
|
||||||
return dummySuggestions.map((suggestion, index) => (
|
}`}
|
||||||
<button
|
>
|
||||||
key={suggestion}
|
<Search size={14} className="text-gray-400 shrink-0" />
|
||||||
onClick={() => {
|
<span>{suggestion}</span>
|
||||||
setSearchInput(suggestion);
|
</button>
|
||||||
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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -854,38 +854,28 @@ function Schedule() {
|
||||||
</div>
|
</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' }}>
|
<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 ? (
|
{suggestions.map((suggestion, index) => (
|
||||||
<div className="px-4 py-3 text-gray-400 text-sm text-center">
|
<button
|
||||||
검색 중...
|
key={index}
|
||||||
</div>
|
onClick={() => {
|
||||||
) : suggestions.length === 0 ? (
|
setSearchInput(suggestion);
|
||||||
<div className="px-4 py-3 text-gray-400 text-sm text-center">
|
setSearchTerm(suggestion);
|
||||||
추천 검색어가 없습니다
|
setShowSuggestions(false);
|
||||||
</div>
|
setSelectedSuggestionIndex(-1);
|
||||||
) : (
|
}}
|
||||||
suggestions.map((suggestion, index) => (
|
onMouseEnter={() => setSelectedSuggestionIndex(index)}
|
||||||
<button
|
className={`w-full px-4 py-2.5 text-left flex items-center gap-3 transition-colors ${
|
||||||
key={index}
|
selectedSuggestionIndex === index
|
||||||
onClick={() => {
|
? 'bg-primary/10 text-primary'
|
||||||
setSearchInput(suggestion);
|
: 'hover:bg-gray-50 text-gray-700'
|
||||||
setSearchTerm(suggestion);
|
}`}
|
||||||
setShowSuggestions(false);
|
>
|
||||||
setSelectedSuggestionIndex(-1);
|
<Search size={15} className={selectedSuggestionIndex === index ? 'text-primary' : 'text-gray-400'} />
|
||||||
}}
|
<span className="text-sm">{suggestion}</span>
|
||||||
onMouseEnter={() => setSelectedSuggestionIndex(index)}
|
</button>
|
||||||
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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue