catalog prices fix
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
import { PartsIndexCatalogsResponse, PartsIndexGroup, PartsIndexEntityInfoResponse } from '@/types/partsindex';
|
import { PartsIndexCatalogsResponse, PartsIndexGroup, PartsIndexEntityInfoResponse } from '@/types/partsindex';
|
||||||
|
|
||||||
const PARTS_INDEX_API_BASE = 'https://api.parts-index.com';
|
const PARTS_INDEX_API_BASE = process.env.PARTSAPI_URL+"/v1" || 'https://api.parts-index.com/v1';
|
||||||
const API_KEY = 'PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE';
|
const API_KEY = 'PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE';
|
||||||
|
|
||||||
class PartsIndexService {
|
class PartsIndexService {
|
||||||
|
@ -38,8 +38,8 @@ const mockData = Array(12).fill({
|
|||||||
brand: "Borsehung",
|
brand: "Borsehung",
|
||||||
});
|
});
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 50; // Целевое количество товаров на странице
|
const ITEMS_PER_PAGE = 50; // Уменьшено для быстрой загрузки и лучшего UX
|
||||||
const PARTSINDEX_PAGE_SIZE = 25; // Размер страницы PartsIndex API (фиксированный)
|
const PARTSINDEX_PAGE_SIZE = 25; // Синхронизировано для оптимальной скорости
|
||||||
const MAX_BRANDS_DISPLAY = 10; // Сколько брендов показывать изначально
|
const MAX_BRANDS_DISPLAY = 10; // Сколько брендов показывать изначально
|
||||||
|
|
||||||
export default function Catalog() {
|
export default function Catalog() {
|
||||||
@ -175,16 +175,17 @@ export default function Catalog() {
|
|||||||
// Хук для загрузки цен товаров PartsIndex
|
// Хук для загрузки цен товаров PartsIndex
|
||||||
const { getPrice, isLoadingPrice, ensurePriceLoaded } = useProductPrices();
|
const { getPrice, isLoadingPrice, ensurePriceLoaded } = useProductPrices();
|
||||||
|
|
||||||
// Загружаем цены для видимых товаров PartsIndex
|
// Загружаем цены для видимых товаров PartsIndex (для отображения конкретных цен)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isPartsIndexMode && visibleEntities.length > 0) {
|
if (isPartsIndexMode && visibleEntities.length > 0) {
|
||||||
|
// Загружаем цены только для видимых товаров для отображения точных цен
|
||||||
visibleEntities.forEach((entity, index) => {
|
visibleEntities.forEach((entity, index) => {
|
||||||
const productForPrice = {
|
const productForPrice = {
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
code: entity.code,
|
code: entity.code,
|
||||||
brand: entity.brand.name
|
brand: entity.brand.name
|
||||||
};
|
};
|
||||||
// Загружаем с небольшой задержкой чтобы не перегружать сервер
|
// Загружаем с небольшой задержкой
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
ensurePriceLoaded(productForPrice);
|
ensurePriceLoaded(productForPrice);
|
||||||
}, index * 50);
|
}, index * 50);
|
||||||
@ -284,25 +285,18 @@ export default function Catalog() {
|
|||||||
// Восстанавливаем автоподгрузку
|
// Восстанавливаем автоподгрузку
|
||||||
console.log('🔄 Автоподгрузка активна');
|
console.log('🔄 Автоподгрузка активна');
|
||||||
|
|
||||||
// Подсчитываем текущее количество товаров с предложениями
|
// Подсчитываем текущее количество товаров (все уже отфильтрованы на сервере)
|
||||||
const currentEntitiesWithOffers = accumulatedEntities.filter(entity => {
|
const currentEntitiesCount = accumulatedEntities.length;
|
||||||
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
|
|
||||||
const priceData = getPrice(productForPrice);
|
|
||||||
const isLoadingPriceData = isLoadingPrice(productForPrice);
|
|
||||||
// Товар считается "с предложениями" если у него есть реальная цена (не null и не undefined)
|
|
||||||
return (priceData && priceData.price && priceData.price > 0) || isLoadingPriceData;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📊 Автоподгрузка: текущее состояние:', {
|
console.log('📊 Автоподгрузка: текущее состояние:', {
|
||||||
накопленоТоваров: accumulatedEntities.length,
|
накопленоТоваров: currentEntitiesCount,
|
||||||
сПредложениями: currentEntitiesWithOffers.length,
|
|
||||||
целевоеКоличество: ITEMS_PER_PAGE,
|
целевоеКоличество: ITEMS_PER_PAGE,
|
||||||
естьЕщеТовары: hasMoreEntities
|
естьЕщеТовары: hasMoreEntities
|
||||||
});
|
});
|
||||||
|
|
||||||
// Если у нас уже достаточно товаров с предложениями, не загружаем
|
// Если у нас уже достаточно товаров, не загружаем
|
||||||
if (currentEntitiesWithOffers.length >= ITEMS_PER_PAGE) {
|
if (currentEntitiesCount >= ITEMS_PER_PAGE) {
|
||||||
console.log('✅ Автоподгрузка: достаточно товаров с предложениями');
|
console.log('✅ Автоподгрузка: достаточно товаров');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,26 +438,18 @@ export default function Catalog() {
|
|||||||
}
|
}
|
||||||
}, [isPartsIndexMode, entitiesWithOffers.length, hasMoreEntities, isAutoLoading]);
|
}, [isPartsIndexMode, entitiesWithOffers.length, hasMoreEntities, isAutoLoading]);
|
||||||
|
|
||||||
// Обновляем список товаров с предложениями при изменении накопленных товаров или цен
|
// Обновляем список товаров при изменении накопленных товаров (серверная фильтрация)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPartsIndexMode) {
|
if (!isPartsIndexMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Показываем все товары, но отдельно считаем те, у которых есть цены
|
// Все товары уже отфильтрованы на сервере - показываем все накопленные
|
||||||
const entitiesWithOffers = accumulatedEntities;
|
const entitiesWithOffers = accumulatedEntities;
|
||||||
|
|
||||||
// Подсчитываем количество товаров с реальными ценами для автоподгрузки
|
|
||||||
const entitiesWithRealPrices = accumulatedEntities.filter(entity => {
|
|
||||||
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
|
|
||||||
const priceData = getPrice(productForPrice);
|
|
||||||
return priceData && priceData.price && priceData.price > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📊 Обновляем entitiesWithOffers:', {
|
console.log('📊 Обновляем entitiesWithOffers (серверная фильтрация):', {
|
||||||
накопленоТоваров: accumulatedEntities.length,
|
накопленоТоваров: accumulatedEntities.length,
|
||||||
отображаемыхТоваров: entitiesWithOffers.length,
|
отображаемыхТоваров: entitiesWithOffers.length,
|
||||||
сРеальнымиЦенами: entitiesWithRealPrices.length,
|
|
||||||
целевоеКоличество: ITEMS_PER_PAGE
|
целевоеКоличество: ITEMS_PER_PAGE
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -486,29 +472,7 @@ export default function Catalog() {
|
|||||||
|
|
||||||
}, [isPartsIndexMode, accumulatedEntities, currentUserPage]);
|
}, [isPartsIndexMode, accumulatedEntities, currentUserPage]);
|
||||||
|
|
||||||
// Отдельный useEffect для обновления статистики цен (без влияния на visibleEntities)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isPartsIndexMode || accumulatedEntities.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обновляем статистику каждые 2 секунды
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
const entitiesWithRealPrices = accumulatedEntities.filter(entity => {
|
|
||||||
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
|
|
||||||
const priceData = getPrice(productForPrice);
|
|
||||||
return priceData && priceData.price && priceData.price > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('💰 Обновление статистики цен:', {
|
|
||||||
накопленоТоваров: accumulatedEntities.length,
|
|
||||||
сРеальнымиЦенами: entitiesWithRealPrices.length,
|
|
||||||
процентЗагрузки: Math.round((entitiesWithRealPrices.length / accumulatedEntities.length) * 100)
|
|
||||||
});
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [isPartsIndexMode, accumulatedEntities.length, getPrice]);
|
|
||||||
|
|
||||||
// Генерируем динамические фильтры для PartsAPI
|
// Генерируем динамические фильтры для PartsAPI
|
||||||
const generatePartsAPIFilters = useCallback((): FilterConfig[] => {
|
const generatePartsAPIFilters = useCallback((): FilterConfig[] => {
|
||||||
@ -724,12 +688,18 @@ export default function Catalog() {
|
|||||||
} else if (isPartsIndexMode && !entitiesLoading && !entitiesError) {
|
} else if (isPartsIndexMode && !entitiesLoading && !entitiesError) {
|
||||||
// Для PartsIndex показываем пустое состояние если нет товаров И данные уже загружены
|
// Для PartsIndex показываем пустое состояние если нет товаров И данные уже загружены
|
||||||
const hasLoadedData = accumulatedEntities.length > 0 || Boolean(entitiesData?.partsIndexCatalogEntities?.list);
|
const hasLoadedData = accumulatedEntities.length > 0 || Boolean(entitiesData?.partsIndexCatalogEntities?.list);
|
||||||
setShowEmptyState(hasLoadedData && visibleEntities.length === 0);
|
|
||||||
console.log('📊 Определяем showEmptyState для PartsIndex:', {
|
// Показываем пустое состояние если данные загружены и нет видимых товаров
|
||||||
|
// (товары уже отфильтрованы на сервере, поэтому не нужно ждать загрузки цен)
|
||||||
|
const shouldShowEmpty = hasLoadedData && visibleEntities.length === 0;
|
||||||
|
setShowEmptyState(shouldShowEmpty);
|
||||||
|
|
||||||
|
console.log('📊 Определяем showEmptyState для PartsIndex (серверная фильтрация):', {
|
||||||
hasLoadedData,
|
hasLoadedData,
|
||||||
visibleEntitiesLength: visibleEntities.length,
|
visibleEntitiesLength: visibleEntities.length,
|
||||||
accumulatedEntitiesLength: accumulatedEntities.length,
|
accumulatedEntitiesLength: accumulatedEntities.length,
|
||||||
showEmptyState: hasLoadedData && visibleEntities.length === 0
|
shouldShowEmpty,
|
||||||
|
showEmptyState: shouldShowEmpty
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setShowEmptyState(false);
|
setShowEmptyState(false);
|
||||||
@ -936,6 +906,8 @@ export default function Catalog() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Сообщение об ошибке */}
|
{/* Сообщение об ошибке */}
|
||||||
{isPartsAPIMode && articlesError && (
|
{isPartsAPIMode && articlesError && (
|
||||||
<div className="flex justify-center items-center py-8">
|
<div className="flex justify-center items-center py-8">
|
||||||
@ -1001,16 +973,20 @@ export default function Catalog() {
|
|||||||
const priceData = getPrice(productForPrice);
|
const priceData = getPrice(productForPrice);
|
||||||
const isLoadingPriceData = isLoadingPrice(productForPrice);
|
const isLoadingPriceData = isLoadingPrice(productForPrice);
|
||||||
|
|
||||||
// Определяем цену для отображения
|
// Определяем цену для отображения (все товары уже отфильтрованы на сервере)
|
||||||
let displayPrice = "Цена по запросу";
|
let displayPrice = "";
|
||||||
let displayCurrency = "RUB";
|
let displayCurrency = "RUB";
|
||||||
let priceElement;
|
let priceElement;
|
||||||
|
|
||||||
if (isLoadingPriceData) {
|
if (isLoadingPriceData) {
|
||||||
|
// Показываем скелетон загрузки вместо текста
|
||||||
priceElement = <PriceSkeleton />;
|
priceElement = <PriceSkeleton />;
|
||||||
} else if (priceData && priceData.price) {
|
} else if (priceData && priceData.price) {
|
||||||
displayPrice = `${priceData.price.toLocaleString('ru-RU')} ₽`;
|
displayPrice = `${priceData.price.toLocaleString('ru-RU')} ₽`;
|
||||||
displayCurrency = priceData.currency || "RUB";
|
displayCurrency = priceData.currency || "RUB";
|
||||||
|
} else {
|
||||||
|
// Если нет данных о цене, показываем скелетон (товар должен загрузиться)
|
||||||
|
priceElement = <PriceSkeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -1021,7 +997,7 @@ export default function Catalog() {
|
|||||||
articleNumber={entity.code}
|
articleNumber={entity.code}
|
||||||
brandName={entity.brand.name}
|
brandName={entity.brand.name}
|
||||||
image={entity.images?.[0] || ''}
|
image={entity.images?.[0] || ''}
|
||||||
price={isLoadingPriceData ? "" : displayPrice}
|
price={priceElement ? "" : displayPrice}
|
||||||
priceElement={priceElement}
|
priceElement={priceElement}
|
||||||
oldPrice=""
|
oldPrice=""
|
||||||
discount=""
|
discount=""
|
||||||
|
@ -9,7 +9,7 @@ async function testPartsIndexAPI() {
|
|||||||
|
|
||||||
// Получаем каталоги
|
// Получаем каталоги
|
||||||
console.log('\n📦 Получаем список каталогов...');
|
console.log('\n📦 Получаем список каталогов...');
|
||||||
const catalogsResponse = await fetch('https://api.parts-index.com/v1/catalogs?lang=ru', {
|
const catalogsResponse = await fetch(process.env.PARTSAPI_URL+"/v1/catalogs?lang=ru", {
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
},
|
},
|
||||||
@ -31,7 +31,7 @@ async function testPartsIndexAPI() {
|
|||||||
console.log(`\n🎯 Получаем группы для каталога "${firstCatalog.name}"...`);
|
console.log(`\n🎯 Получаем группы для каталога "${firstCatalog.name}"...`);
|
||||||
|
|
||||||
const groupsResponse = await fetch(
|
const groupsResponse = await fetch(
|
||||||
`https://api.parts-index.com/v1/catalogs/${firstCatalog.id}/groups?lang=ru`,
|
`${process.env.PARTSAPI_URL}/v1/catalogs/${firstCatalog.id}/groups?lang=ru`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
21
user_input.py
Normal file
21
user_input.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
def main():
|
||||||
|
while True:
|
||||||
|
print("\n" + "="*50)
|
||||||
|
user_input = input("Please provide feedback or next task (type 'stop' to exit): ").strip()
|
||||||
|
|
||||||
|
if user_input.lower() == 'stop':
|
||||||
|
print("Exiting task loop. Thank you!")
|
||||||
|
break
|
||||||
|
elif user_input.lower() == '':
|
||||||
|
print("Please provide some input or type 'stop' to exit.")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print(f"\nReceived input: {user_input}")
|
||||||
|
print("Processing your request...")
|
||||||
|
# Here the main process would handle the user's input
|
||||||
|
return user_input
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
result = main()
|
||||||
|
if result and result.lower() != 'stop':
|
||||||
|
print(f"Next task received: {result}")
|
Reference in New Issue
Block a user