diff --git a/src/components/FiltersPanelMobile.tsx b/src/components/FiltersPanelMobile.tsx index 3eaa07e..a745c77 100644 --- a/src/components/FiltersPanelMobile.tsx +++ b/src/components/FiltersPanelMobile.tsx @@ -63,6 +63,7 @@ const FiltersPanelMobile: React.FC = ({ setLocalFilterValues({}); onSearchChange(''); // Сбрасываем фильтры в родительском компоненте + // Используем пустые массивы для правильной очистки Object.keys(filterValues).forEach(key => { onFilterChange?.(key, []); }); diff --git a/src/pages/catalog.tsx b/src/pages/catalog.tsx index 5cbd174..3de49db 100644 --- a/src/pages/catalog.tsx +++ b/src/pages/catalog.tsx @@ -56,6 +56,36 @@ export default function Catalog() { const [showSortMobile, setShowSortMobile] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [selectedFilters, setSelectedFilters] = useState<{[key: string]: string[]}>({}); + + // Инициализация фильтров из URL при загрузке + useEffect(() => { + if (router.isReady) { + const urlFilters: {[key: string]: string[]} = {}; + const urlSearchQuery = router.query.q as string || ''; + + // Восстанавливаем фильтры из URL + Object.keys(router.query).forEach(key => { + if (key.startsWith('filter_')) { + const filterName = key.replace('filter_', ''); + const filterValue = router.query[key]; + if (typeof filterValue === 'string') { + urlFilters[filterName] = [filterValue]; + } else if (Array.isArray(filterValue)) { + urlFilters[filterName] = filterValue; + } + } + }); + + console.log('🔗 Восстанавливаем фильтры из URL:', { urlFilters, urlSearchQuery }); + + if (Object.keys(urlFilters).length > 0) { + setSelectedFilters(urlFilters); + } + if (urlSearchQuery) { + setSearchQuery(urlSearchQuery); + } + } + }, [router.isReady]); const [visibleArticles, setVisibleArticles] = useState([]); const [visibleEntities, setVisibleEntities] = useState([]); const [currentPage, setCurrentPage] = useState(1); @@ -80,6 +110,7 @@ export default function Catalog() { const [isAutoLoading, setIsAutoLoading] = useState(false); // Автоматическая подгрузка в процессе const [currentUserPage, setCurrentUserPage] = useState(1); // Текущая пользовательская страница const [entitiesCache, setEntitiesCache] = useState>(new Map()); // Кэш страниц + const [isFilterChanging, setIsFilterChanging] = useState(false); // Флаг изменения фильтров // Карта видимости товаров по индексу const [visibilityMap, setVisibilityMap] = useState>(new Map()); @@ -209,9 +240,16 @@ export default function Catalog() { console.log('📊 Обновляем entitiesData:', { listLength: entitiesData.partsIndexCatalogEntities.list.length, pagination: entitiesData.partsIndexCatalogEntities.pagination, - currentPage: entitiesData.partsIndexCatalogEntities.pagination?.page?.current || 1 + currentPage: entitiesData.partsIndexCatalogEntities.pagination?.page?.current || 1, + isFilterChanging }); + // Если изменяются фильтры, сбрасываем флаг после получения новых данных + if (isFilterChanging) { + setIsFilterChanging(false); + console.log('🔄 Сброшен флаг isFilterChanging - получены новые отфильтрованные данные'); + } + const newEntities = entitiesData.partsIndexCatalogEntities.list; const pagination = entitiesData.partsIndexCatalogEntities.pagination; @@ -229,9 +267,13 @@ export default function Catalog() { // Если это первая страница или сброс, заменяем накопленные товары if (currentPage === 1) { setAccumulatedEntities(newEntities); - // Устанавливаем visibleEntities сразу, не дожидаясь проверки цен - setVisibleEntities(newEntities); - console.log('✅ Установлены visibleEntities для первой страницы:', newEntities.length); + // Устанавливаем visibleEntities сразу, только если не идет изменение фильтров + if (!isFilterChanging) { + setVisibleEntities(newEntities); + console.log('✅ Установлены visibleEntities для первой страницы:', newEntities.length); + } else { + console.log('🔄 Пропускаем установку visibleEntities - фильтры изменяются'); + } } else { // Добавляем к накопленным товарам setAccumulatedEntities(prev => [...prev, ...newEntities]); @@ -246,7 +288,7 @@ export default function Catalog() { console.log('✅ Пагинация обновлена:', { currentPage, hasNext, hasPrev }); } - }, [entitiesData]); + }, [entitiesData, isFilterChanging]); // Преобразование выбранных фильтров в формат PartsIndex API const convertFiltersToPartsIndexParams = useMemo((): Record => { @@ -444,13 +486,20 @@ export default function Catalog() { return; } + // Если фильтры изменяются, не обновляем отображение старых данных + if (isFilterChanging) { + console.log('🔄 Пропускаем обновление entitiesWithOffers - фильтры изменяются'); + return; + } + // Все товары уже отфильтрованы на сервере - показываем все накопленные const entitiesWithOffers = accumulatedEntities; console.log('📊 Обновляем entitiesWithOffers (серверная фильтрация):', { накопленоТоваров: accumulatedEntities.length, отображаемыхТоваров: entitiesWithOffers.length, - целевоеКоличество: ITEMS_PER_PAGE + целевоеКоличество: ITEMS_PER_PAGE, + isFilterChanging }); setEntitiesWithOffers(entitiesWithOffers); @@ -470,7 +519,7 @@ export default function Catalog() { setVisibleEntities(visibleForCurrentPage); - }, [isPartsIndexMode, accumulatedEntities, currentUserPage]); + }, [isPartsIndexMode, accumulatedEntities, currentUserPage, isFilterChanging]); @@ -559,27 +608,94 @@ export default function Catalog() { + // Функция для обновления URL с фильтрами + const updateUrlWithFilters = useCallback((filters: {[key: string]: string[]}, search: string) => { + const query: any = { ...router.query }; + + // Удаляем старые фильтры из URL + Object.keys(query).forEach(key => { + if (key.startsWith('filter_') || key === 'q') { + delete query[key]; + } + }); + + // Добавляем новые фильтры + Object.entries(filters).forEach(([filterName, values]) => { + if (values.length > 0) { + query[`filter_${filterName}`] = values.length === 1 ? values[0] : values; + } + }); + + // Добавляем поисковый запрос + if (search.trim()) { + query.q = search; + } + + // Обновляем URL без перезагрузки страницы + router.push({ + pathname: router.pathname, + query + }, undefined, { shallow: true }); + }, [router]); + const handleDesktopFilterChange = (filterTitle: string, value: string | string[]) => { - setSelectedFilters(prev => ({ - ...prev, - [filterTitle]: Array.isArray(value) ? value : [value] - })); + setSelectedFilters(prev => { + const newFilters = { ...prev }; + + // Если значение пустое (пустой массив или пустая строка), удаляем фильтр + if (Array.isArray(value) && value.length === 0) { + delete newFilters[filterTitle]; + } else if (!value || (typeof value === 'string' && value.trim() === '')) { + delete newFilters[filterTitle]; + } else { + // Иначе устанавливаем значение + newFilters[filterTitle] = Array.isArray(value) ? value : [value]; + } + + // Обновляем URL + updateUrlWithFilters(newFilters, searchQuery); + + return newFilters; + }); }; const handleMobileFilterChange = (type: string, value: any) => { - setSelectedFilters(prev => ({ - ...prev, - [type]: Array.isArray(value) ? value : [value] - })); + setSelectedFilters(prev => { + const newFilters = { ...prev }; + + // Если значение пустое (пустой массив или пустая строка), удаляем фильтр + if (Array.isArray(value) && value.length === 0) { + delete newFilters[type]; + } else if (!value || (typeof value === 'string' && value.trim() === '')) { + delete newFilters[type]; + } else { + // Иначе устанавливаем значение + newFilters[type] = Array.isArray(value) ? value : [value]; + } + + // Обновляем URL + updateUrlWithFilters(newFilters, searchQuery); + + return newFilters; + }); }; + // Обработчик изменения поискового запроса + const handleSearchChange = useCallback((value: string) => { + setSearchQuery(value); + updateUrlWithFilters(selectedFilters, value); + }, [selectedFilters, updateUrlWithFilters]); + // Функция для сброса всех фильтров const handleResetFilters = useCallback(() => { setSearchQuery(''); setSelectedFilters({}); setShowAllBrands(false); setPartsIndexPage(1); // Сбрасываем страницу PartsIndex на первую - }, []); + + // Очищаем URL от фильтров + updateUrlWithFilters({}, ''); + }, [updateUrlWithFilters]); // Фильтрация по поиску и фильтрам для PartsAPI const filteredArticles = useMemo(() => { @@ -636,6 +752,10 @@ export default function Catalog() { // Если изменился поисковый запрос или фильтры, нужно перезагрузить данные с сервера if (searchQuery.trim() || Object.keys(selectedFilters).length > 0) { console.log('🔍 Поисковый запрос или фильтры изменились, сбрасываем пагинацию'); + + // Устанавливаем флаг изменения фильтров + setIsFilterChanging(true); + setPartsIndexPage(1); setCurrentUserPage(1); setHasMoreEntities(true); @@ -643,10 +763,36 @@ export default function Catalog() { setEntitiesWithOffers([]); setEntitiesCache(new Map()); - // Перезагружаем данные с новыми параметрами фильтрации - const apiParams = convertFiltersToPartsIndexParams; + // Вычисляем параметры фильтрации прямо здесь, чтобы избежать зависимости от useMemo + let apiParams: Record = {}; + if (paramsData?.partsIndexCatalogParams?.list && Object.keys(selectedFilters).length > 0) { + paramsData.partsIndexCatalogParams.list.forEach((param: any) => { + const selectedValues = selectedFilters[param.name]; + if (selectedValues && selectedValues.length > 0) { + // Находим соответствующие значения из API данных + const matchingValues = param.values.filter((value: any) => + selectedValues.includes(value.title || value.value) + ); + + if (matchingValues.length > 0) { + // Используем ID параметра из API и значения + apiParams[param.id] = matchingValues.map((v: any) => v.value); + } + } + }); + } + const paramsString = Object.keys(apiParams).length > 0 ? JSON.stringify(apiParams) : undefined; + console.log('🔄 Запуск refetch с новыми фильтрами:', { + searchQuery, + selectedFilters, + apiParams, + paramsString, + catalogId, + groupId + }); + // Также обновляем параметры фильтрации refetchParams({ catalogId: catalogId as string, @@ -654,6 +800,10 @@ export default function Catalog() { lang: 'ru', q: searchQuery || undefined, params: paramsString + }).then(result => { + console.log('✅ refetchParams результат:', result); + }).catch(error => { + console.error('❌ refetchParams ошибка:', error); }); refetchEntities({ @@ -664,11 +814,20 @@ export default function Catalog() { page: 1, q: searchQuery || undefined, params: paramsString + }).then(result => { + console.log('✅ refetchEntities результат:', result.data?.partsIndexCatalogEntities?.list?.length || 0, 'товаров'); + }).catch(error => { + console.error('❌ refetchEntities ошибка:', error); }); + } else { + // Если нет активных фильтров, сбрасываем флаг + if (isFilterChanging) { + setIsFilterChanging(false); + } } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isPartsIndexMode, searchQuery, JSON.stringify(selectedFilters), refetchEntities, refetchParams, convertFiltersToPartsIndexParams]); + }, [isPartsIndexMode, searchQuery, JSON.stringify(selectedFilters), paramsData]); // Управляем показом пустого состояния с задержкой useEffect(() => { @@ -854,7 +1013,7 @@ export default function Catalog() { onFilterChange={handleDesktopFilterChange} filterValues={selectedFilters} searchQuery={searchQuery} - onSearchChange={setSearchQuery} + onSearchChange={handleSearchChange} isLoading={filtersGenerating} /> @@ -865,7 +1024,7 @@ export default function Catalog() { onFilterChange={handleDesktopFilterChange} filterValues={selectedFilters} searchQuery={searchQuery} - onSearchChange={setSearchQuery} + onSearchChange={handleSearchChange} isLoading={filtersLoading} /> @@ -876,7 +1035,7 @@ export default function Catalog() { onFilterChange={handleDesktopFilterChange} filterValues={selectedFilters} searchQuery={searchQuery} - onSearchChange={setSearchQuery} + onSearchChange={handleSearchChange} isLoading={filtersLoading} /> @@ -886,7 +1045,7 @@ export default function Catalog() { onClose={() => setShowFiltersMobile(false)} filters={isPartsAPIMode ? dynamicFilters : catalogFilters} searchQuery={searchQuery} - onSearchChange={setSearchQuery} + onSearchChange={handleSearchChange} filterValues={selectedFilters} onFilterChange={handleMobileFilterChange} /> @@ -957,12 +1116,21 @@ export default function Catalog() { )} + {/* Показываем индикатор загрузки при изменении фильтров */} + {isPartsIndexMode && isFilterChanging && ( +
+ +
Применяем фильтры...
+
+ )} + {/* Отображение товаров PartsIndex */} - {isPartsIndexMode && (() => { + {isPartsIndexMode && !isFilterChanging && (() => { console.log('🎯 Проверяем отображение PartsIndex товаров:', { isPartsIndexMode, visibleEntitiesLength: visibleEntities.length, - visibleEntities: visibleEntities.map(e => ({ id: e.id, code: e.code, brand: e.brand.name })) + visibleEntities: visibleEntities.map(e => ({ id: e.id, code: e.code, brand: e.brand.name })), + isFilterChanging }); return visibleEntities.length > 0; })() && (