fixed prices, but still working on filters

This commit is contained in:
54CHA
2025-07-18 04:22:37 +03:00
parent b6f9d017d6
commit b7edd73ce0
2 changed files with 194 additions and 25 deletions

View File

@ -63,6 +63,7 @@ const FiltersPanelMobile: React.FC<FiltersPanelMobileProps> = ({
setLocalFilterValues({}); setLocalFilterValues({});
onSearchChange(''); onSearchChange('');
// Сбрасываем фильтры в родительском компоненте // Сбрасываем фильтры в родительском компоненте
// Используем пустые массивы для правильной очистки
Object.keys(filterValues).forEach(key => { Object.keys(filterValues).forEach(key => {
onFilterChange?.(key, []); onFilterChange?.(key, []);
}); });

View File

@ -56,6 +56,36 @@ export default function Catalog() {
const [showSortMobile, setShowSortMobile] = useState(false); const [showSortMobile, setShowSortMobile] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [selectedFilters, setSelectedFilters] = useState<{[key: string]: string[]}>({}); 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<PartsAPIArticle[]>([]); const [visibleArticles, setVisibleArticles] = useState<PartsAPIArticle[]>([]);
const [visibleEntities, setVisibleEntities] = useState<PartsIndexEntity[]>([]); const [visibleEntities, setVisibleEntities] = useState<PartsIndexEntity[]>([]);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
@ -80,6 +110,7 @@ export default function Catalog() {
const [isAutoLoading, setIsAutoLoading] = useState(false); // Автоматическая подгрузка в процессе const [isAutoLoading, setIsAutoLoading] = useState(false); // Автоматическая подгрузка в процессе
const [currentUserPage, setCurrentUserPage] = useState(1); // Текущая пользовательская страница const [currentUserPage, setCurrentUserPage] = useState(1); // Текущая пользовательская страница
const [entitiesCache, setEntitiesCache] = useState<Map<number, PartsIndexEntity[]>>(new Map()); // Кэш страниц const [entitiesCache, setEntitiesCache] = useState<Map<number, PartsIndexEntity[]>>(new Map()); // Кэш страниц
const [isFilterChanging, setIsFilterChanging] = useState(false); // Флаг изменения фильтров
// Карта видимости товаров по индексу // Карта видимости товаров по индексу
const [visibilityMap, setVisibilityMap] = useState<Map<number, boolean>>(new Map()); const [visibilityMap, setVisibilityMap] = useState<Map<number, boolean>>(new Map());
@ -209,9 +240,16 @@ export default function Catalog() {
console.log('📊 Обновляем entitiesData:', { console.log('📊 Обновляем entitiesData:', {
listLength: entitiesData.partsIndexCatalogEntities.list.length, listLength: entitiesData.partsIndexCatalogEntities.list.length,
pagination: entitiesData.partsIndexCatalogEntities.pagination, 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 newEntities = entitiesData.partsIndexCatalogEntities.list;
const pagination = entitiesData.partsIndexCatalogEntities.pagination; const pagination = entitiesData.partsIndexCatalogEntities.pagination;
@ -229,9 +267,13 @@ export default function Catalog() {
// Если это первая страница или сброс, заменяем накопленные товары // Если это первая страница или сброс, заменяем накопленные товары
if (currentPage === 1) { if (currentPage === 1) {
setAccumulatedEntities(newEntities); setAccumulatedEntities(newEntities);
// Устанавливаем visibleEntities сразу, не дожидаясь проверки цен // Устанавливаем visibleEntities сразу, только если не идет изменение фильтров
setVisibleEntities(newEntities); if (!isFilterChanging) {
console.log('✅ Установлены visibleEntities для первой страницы:', newEntities.length); setVisibleEntities(newEntities);
console.log('✅ Установлены visibleEntities для первой страницы:', newEntities.length);
} else {
console.log('🔄 Пропускаем установку visibleEntities - фильтры изменяются');
}
} else { } else {
// Добавляем к накопленным товарам // Добавляем к накопленным товарам
setAccumulatedEntities(prev => [...prev, ...newEntities]); setAccumulatedEntities(prev => [...prev, ...newEntities]);
@ -246,7 +288,7 @@ export default function Catalog() {
console.log('✅ Пагинация обновлена:', { currentPage, hasNext, hasPrev }); console.log('✅ Пагинация обновлена:', { currentPage, hasNext, hasPrev });
} }
}, [entitiesData]); }, [entitiesData, isFilterChanging]);
// Преобразование выбранных фильтров в формат PartsIndex API // Преобразование выбранных фильтров в формат PartsIndex API
const convertFiltersToPartsIndexParams = useMemo((): Record<string, any> => { const convertFiltersToPartsIndexParams = useMemo((): Record<string, any> => {
@ -444,13 +486,20 @@ export default function Catalog() {
return; return;
} }
// Если фильтры изменяются, не обновляем отображение старых данных
if (isFilterChanging) {
console.log('🔄 Пропускаем обновление entitiesWithOffers - фильтры изменяются');
return;
}
// Все товары уже отфильтрованы на сервере - показываем все накопленные // Все товары уже отфильтрованы на сервере - показываем все накопленные
const entitiesWithOffers = accumulatedEntities; const entitiesWithOffers = accumulatedEntities;
console.log('📊 Обновляем entitiesWithOffers (серверная фильтрация):', { console.log('📊 Обновляем entitiesWithOffers (серверная фильтрация):', {
накопленоТоваров: accumulatedEntities.length, накопленоТоваров: accumulatedEntities.length,
отображаемыхТоваров: entitiesWithOffers.length, отображаемыхТоваров: entitiesWithOffers.length,
целевоеКоличество: ITEMS_PER_PAGE целевоеКоличество: ITEMS_PER_PAGE,
isFilterChanging
}); });
setEntitiesWithOffers(entitiesWithOffers); setEntitiesWithOffers(entitiesWithOffers);
@ -470,7 +519,7 @@ export default function Catalog() {
setVisibleEntities(visibleForCurrentPage); 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[]) => { const handleDesktopFilterChange = (filterTitle: string, value: string | string[]) => {
setSelectedFilters(prev => ({ setSelectedFilters(prev => {
...prev, const newFilters = { ...prev };
[filterTitle]: Array.isArray(value) ? value : [value]
})); // Если значение пустое (пустой массив или пустая строка), удаляем фильтр
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) => { const handleMobileFilterChange = (type: string, value: any) => {
setSelectedFilters(prev => ({ setSelectedFilters(prev => {
...prev, const newFilters = { ...prev };
[type]: Array.isArray(value) ? value : [value]
})); // Если значение пустое (пустой массив или пустая строка), удаляем фильтр
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(() => { const handleResetFilters = useCallback(() => {
setSearchQuery(''); setSearchQuery('');
setSelectedFilters({}); setSelectedFilters({});
setShowAllBrands(false); setShowAllBrands(false);
setPartsIndexPage(1); // Сбрасываем страницу PartsIndex на первую setPartsIndexPage(1); // Сбрасываем страницу PartsIndex на первую
}, []);
// Очищаем URL от фильтров
updateUrlWithFilters({}, '');
}, [updateUrlWithFilters]);
// Фильтрация по поиску и фильтрам для PartsAPI // Фильтрация по поиску и фильтрам для PartsAPI
const filteredArticles = useMemo(() => { const filteredArticles = useMemo(() => {
@ -636,6 +752,10 @@ export default function Catalog() {
// Если изменился поисковый запрос или фильтры, нужно перезагрузить данные с сервера // Если изменился поисковый запрос или фильтры, нужно перезагрузить данные с сервера
if (searchQuery.trim() || Object.keys(selectedFilters).length > 0) { if (searchQuery.trim() || Object.keys(selectedFilters).length > 0) {
console.log('🔍 Поисковый запрос или фильтры изменились, сбрасываем пагинацию'); console.log('🔍 Поисковый запрос или фильтры изменились, сбрасываем пагинацию');
// Устанавливаем флаг изменения фильтров
setIsFilterChanging(true);
setPartsIndexPage(1); setPartsIndexPage(1);
setCurrentUserPage(1); setCurrentUserPage(1);
setHasMoreEntities(true); setHasMoreEntities(true);
@ -643,10 +763,36 @@ export default function Catalog() {
setEntitiesWithOffers([]); setEntitiesWithOffers([]);
setEntitiesCache(new Map()); setEntitiesCache(new Map());
// Перезагружаем данные с новыми параметрами фильтрации // Вычисляем параметры фильтрации прямо здесь, чтобы избежать зависимости от useMemo
const apiParams = convertFiltersToPartsIndexParams; let apiParams: Record<string, any> = {};
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; const paramsString = Object.keys(apiParams).length > 0 ? JSON.stringify(apiParams) : undefined;
console.log('🔄 Запуск refetch с новыми фильтрами:', {
searchQuery,
selectedFilters,
apiParams,
paramsString,
catalogId,
groupId
});
// Также обновляем параметры фильтрации // Также обновляем параметры фильтрации
refetchParams({ refetchParams({
catalogId: catalogId as string, catalogId: catalogId as string,
@ -654,6 +800,10 @@ export default function Catalog() {
lang: 'ru', lang: 'ru',
q: searchQuery || undefined, q: searchQuery || undefined,
params: paramsString params: paramsString
}).then(result => {
console.log('✅ refetchParams результат:', result);
}).catch(error => {
console.error('❌ refetchParams ошибка:', error);
}); });
refetchEntities({ refetchEntities({
@ -664,11 +814,20 @@ export default function Catalog() {
page: 1, page: 1,
q: searchQuery || undefined, q: searchQuery || undefined,
params: paramsString 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 // eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPartsIndexMode, searchQuery, JSON.stringify(selectedFilters), refetchEntities, refetchParams, convertFiltersToPartsIndexParams]); }, [isPartsIndexMode, searchQuery, JSON.stringify(selectedFilters), paramsData]);
// Управляем показом пустого состояния с задержкой // Управляем показом пустого состояния с задержкой
useEffect(() => { useEffect(() => {
@ -854,7 +1013,7 @@ export default function Catalog() {
onFilterChange={handleDesktopFilterChange} onFilterChange={handleDesktopFilterChange}
filterValues={selectedFilters} filterValues={selectedFilters}
searchQuery={searchQuery} searchQuery={searchQuery}
onSearchChange={setSearchQuery} onSearchChange={handleSearchChange}
isLoading={filtersGenerating} isLoading={filtersGenerating}
/> />
</div> </div>
@ -865,7 +1024,7 @@ export default function Catalog() {
onFilterChange={handleDesktopFilterChange} onFilterChange={handleDesktopFilterChange}
filterValues={selectedFilters} filterValues={selectedFilters}
searchQuery={searchQuery} searchQuery={searchQuery}
onSearchChange={setSearchQuery} onSearchChange={handleSearchChange}
isLoading={filtersLoading} isLoading={filtersLoading}
/> />
</div> </div>
@ -876,7 +1035,7 @@ export default function Catalog() {
onFilterChange={handleDesktopFilterChange} onFilterChange={handleDesktopFilterChange}
filterValues={selectedFilters} filterValues={selectedFilters}
searchQuery={searchQuery} searchQuery={searchQuery}
onSearchChange={setSearchQuery} onSearchChange={handleSearchChange}
isLoading={filtersLoading} isLoading={filtersLoading}
/> />
</div> </div>
@ -886,7 +1045,7 @@ export default function Catalog() {
onClose={() => setShowFiltersMobile(false)} onClose={() => setShowFiltersMobile(false)}
filters={isPartsAPIMode ? dynamicFilters : catalogFilters} filters={isPartsAPIMode ? dynamicFilters : catalogFilters}
searchQuery={searchQuery} searchQuery={searchQuery}
onSearchChange={setSearchQuery} onSearchChange={handleSearchChange}
filterValues={selectedFilters} filterValues={selectedFilters}
onFilterChange={handleMobileFilterChange} onFilterChange={handleMobileFilterChange}
/> />
@ -957,12 +1116,21 @@ export default function Catalog() {
</> </>
)} )}
{/* Показываем индикатор загрузки при изменении фильтров */}
{isPartsIndexMode && isFilterChanging && (
<div className="flex flex-col items-center justify-center py-12">
<LoadingSpinner />
<div className="text-gray-500 text-lg mt-4">Применяем фильтры...</div>
</div>
)}
{/* Отображение товаров PartsIndex */} {/* Отображение товаров PartsIndex */}
{isPartsIndexMode && (() => { {isPartsIndexMode && !isFilterChanging && (() => {
console.log('🎯 Проверяем отображение PartsIndex товаров:', { console.log('🎯 Проверяем отображение PartsIndex товаров:', {
isPartsIndexMode, isPartsIndexMode,
visibleEntitiesLength: visibleEntities.length, 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; return visibleEntities.length > 0;
})() && ( })() && (