Добавлено получение истории поиска с автодополнением в компоненте Header. Обновлены обработчики ввода для управления отображением истории и плейсхолдера. Внедрен запрос для получения последних поисковых запросов. Обновлены стили и логика отображения в компоненте Header.

This commit is contained in:
Bivekich
2025-07-14 10:01:09 +03:00
parent 87339d577e
commit 4dfc081214
6 changed files with 337 additions and 105 deletions

View File

@ -9,6 +9,8 @@ import { FIND_LAXIMO_VEHICLE, DOC_FIND_OEM, FIND_LAXIMO_VEHICLE_BY_PLATE_GLOBAL,
import { LaximoVehicleSearchResult, LaximoDocFindOEMResult, LaximoVehiclesByPartResult } from '@/types/laximo';
import Link from "next/link";
import CartButton from './CartButton';
import SearchHistoryDropdown from './SearchHistoryDropdown';
import { GET_RECENT_SEARCH_QUERIES, PartsSearchHistoryItem } from '@/lib/graphql/search-history';
interface HeaderProps {
onOpenAuthModal?: () => void;
@ -25,9 +27,14 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
const [vehiclesByPartResults, setVehiclesByPartResults] = useState<LaximoVehiclesByPartResult | null>(null);
const [searchType, setSearchType] = useState<'vin' | 'oem' | 'plate' | 'text'>('text');
const [oemSearchMode, setOemSearchMode] = useState<'parts' | 'vehicles'>('parts');
const [showSearchHistory, setShowSearchHistory] = useState(false);
const [searchHistoryItems, setSearchHistoryItems] = useState<PartsSearchHistoryItem[]>([]);
const [inputFocused, setInputFocused] = useState(false);
const [showPlaceholder, setShowPlaceholder] = useState(true);
const router = useRouter();
const searchFormRef = useRef<HTMLFormElement>(null);
const searchDropdownRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const isClient = useIsClient();
// Эффект для восстановления поискового запроса из URL
@ -111,11 +118,28 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
}
});
// Запрос для получения истории поиска
const [getSearchHistory, { loading: historyLoading }] = useLazyQuery(GET_RECENT_SEARCH_QUERIES, {
onCompleted: (data) => {
setSearchHistoryItems(data.partsSearchHistory?.items || []);
},
onError: (error) => {
console.error('❌ Ошибка загрузки истории поиска:', error);
setSearchHistoryItems([]);
}
});
// Закрытие результатов при клике вне области
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (searchDropdownRef.current && !searchDropdownRef.current.contains(event.target as Node)) {
setShowResults(false);
setShowSearchHistory(false);
setInputFocused(false);
// Показываем placeholder обратно только если поле пустое
if (searchQuery.trim() === '') {
setShowPlaceholder(true);
}
}
};
@ -356,6 +380,54 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
router.push(url);
};
// Обработчик фокуса на поле ввода
const handleInputFocus = () => {
setInputFocused(true);
setShowResults(false);
setShowPlaceholder(false);
if (searchQuery.trim() === '') {
setShowSearchHistory(true);
getSearchHistory({ variables: { limit: 5 } });
}
};
// Обработчик изменения значения поля ввода
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setSearchQuery(value);
// Управляем placeholder в зависимости от наличия текста
if (value.trim() === '') {
setShowPlaceholder(false); // Скрываем placeholder пока в фокусе
setShowSearchHistory(true);
setShowResults(false);
getSearchHistory({ variables: { limit: 5 } });
} else {
setShowPlaceholder(false); // Скрываем placeholder когда есть текст
setShowSearchHistory(false);
}
};
// Обработчик потери фокуса
const handleInputBlur = () => {
// Показываем placeholder обратно только если поле пустое
if (searchQuery.trim() === '') {
setShowPlaceholder(true);
}
};
// Обработчик клика по элементу истории
const handleHistoryItemClick = (searchQuery: string) => {
setSearchQuery(searchQuery);
setShowSearchHistory(false);
setInputFocused(false);
setShowPlaceholder(false); // Скрываем placeholder так как теперь есть текст
// Фокусируем поле ввода для возможности редактирования
if (searchInputRef.current) {
searchInputRef.current.focus();
}
};
return (
<>
{/* <section className="top_head">
@ -421,7 +493,7 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
</svg></div>
</div>
</div>
<div className="searcj w-form" style={{ position: 'relative' }}>
<div className="searcj w-form" style={{ position: 'relative' }} ref={searchDropdownRef}>
<form
id="custom-search-form"
name="custom-search-form"
@ -444,23 +516,33 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
</div>
</div>
<input
ref={searchInputRef}
className="text-field w-input"
maxLength={256}
name="customSearch"
data-custom-input="true"
placeholder="Введите код запчасти, VIN номер или госномер автомобиля"
placeholder={showPlaceholder ? "Введите код запчасти, VIN номер или госномер автомобиля" : ""}
type="text"
id="customSearchInput"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onChange={handleInputChange}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
disabled={isSearching}
/>
</form>
{/* История поиска */}
<SearchHistoryDropdown
isVisible={showSearchHistory && !showResults}
historyItems={searchHistoryItems}
onItemClick={handleHistoryItemClick}
loading={historyLoading}
/>
{/* Результаты поиска VIN */}
{showResults && searchResults.length > 0 && (searchType === 'vin' || searchType === 'plate') && (
<div
ref={searchDropdownRef}
className="absolute top-full left-0 right-0 bg-white border border-gray-200 rounded-lg shadow-lg mt-2 z-50 max-h-80 overflow-y-auto"
>
<div className="p-3 border-b border-gray-100">