From 855018bd6c94f1be84984dbf3d98e0321fb3c8e8 Mon Sep 17 00:00:00 2001 From: Bivekich Date: Fri, 27 Jun 2025 15:31:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=BE=20=D0=B4=D0=B5=D1=82=D0=B0=D0=BB=D1=8F=D1=85=20?= =?UTF-8?q?=D0=B8=D0=B7=20Parts=20Index=20=D0=B8=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BF?= =?UTF-8?q?=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=8D=D1=82=D0=BE=D0=B9=20=D0=B8=D0=BD=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D0=B8.=20=D0=92=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B5=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5?= =?UTF-8?q?=20=D1=82=D0=B8=D0=BF=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=BC=D0=B8=20Parts=20Index.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARTS_INDEX_INTEGRATION.md | 117 +++++++++++++++++++++++++++++ src/components/CoreProductCard.tsx | 14 +++- src/hooks/usePartsIndex.ts | 40 +++++++++- src/lib/partsindex-service.ts | 37 ++++++++- src/pages/search-result.tsx | 14 +++- src/types/partsindex.ts | 40 ++++++++++ 6 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 PARTS_INDEX_INTEGRATION.md diff --git a/PARTS_INDEX_INTEGRATION.md b/PARTS_INDEX_INTEGRATION.md new file mode 100644 index 0000000..162ed2d --- /dev/null +++ b/PARTS_INDEX_INTEGRATION.md @@ -0,0 +1,117 @@ +# Интеграция Parts Index API + +## Описание + +В проект добавлена интеграция с Parts Index API для отображения детальной информации о деталях, включая: +- Главную фотографию детали +- Штрих-коды +- Технические характеристики +- Категории +- Дополнительные изображения +- Логотип "powered by Parts Index" + +## Реализованные компоненты + +### 1. Типы (`src/types/partsindex.ts`) +- `PartsIndexEntityInfo` - информация о детали +- `PartsIndexEntityInfoResponse` - ответ API +- `PartsIndexEntityInfoVariables` - параметры запроса + +### 2. Сервис (`src/lib/partsindex-service.ts`) +- `getEntityInfo(code, brand?, lang?)` - получение информации о детали + +### 3. Хук (`src/hooks/usePartsIndex.ts`) +- `usePartsIndexEntityInfo(code, brand)` - хук для получения данных + +### 4. Компонент (`src/components/PartsIndexCard.tsx`) +- Отображение карточки с информацией о детали +- Поддержка состояния загрузки +- Адаптивный дизайн + +## Интеграция в страницу поиска + +В файле `src/pages/search-result.tsx` добавлено: + +```tsx +import PartsIndexCard from "@/components/PartsIndexCard"; +import { usePartsIndexEntityInfo } from "@/hooks/usePartsIndex"; + +// В компоненте: +const { entityInfo, loading: partsIndexLoading } = usePartsIndexEntityInfo( + searchQuery || null, + brandQuery || null +); + +// В JSX: +{partsIndexLoading && ( + +)} +{entityInfo && !partsIndexLoading && ( + +)} +``` + +## API Parts Index + +### Endpoint +``` +GET https://api.parts-index.com/v1/entities +``` + +### Параметры +- `code` (обязательный) - артикул детали +- `brand` (опциональный) - бренд +- `lang` (опциональный) - язык (по умолчанию 'ru') + +### Заголовки +``` +Authorization: PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE +Accept: application/json +``` + +### Пример запроса +```bash +curl -H "Authorization: PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE" \ + "https://api.parts-index.com/v1/entities?code=059198405B&brand=VAG&lang=ru" +``` + +## Тестирование + +### URL для тестирования +``` +http://localhost:3002/search-result?article=059198405B&brand=VAG +``` + +### Тестовая HTML страница +Создана страница `test-parts-index.html` для демонстрации работы API без React. + +## Функциональность + +1. **Автоматическая загрузка** - при переходе на страницу результатов поиска +2. **Главная фотография** - отображается первое изображение из массива +3. **Логотип Parts Index** - в правом верхнем углу карточки +4. **Характеристики** - первые 6 параметров из API +5. **Штрих-коды** - все доступные штрих-коды +6. **Дополнительные изображения** - до 4 дополнительных фото +7. **Обработка ошибок** - скрытие изображений при ошибке загрузки + +## Стили + +Компонент использует Tailwind CSS классы для стилизации: +- Адаптивная сетка для характеристик +- Скроллинг для дополнительных изображений +- Состояние загрузки с анимацией +- Обработка ошибок изображений + +## Производительность + +- Загрузка данных только при наличии артикула +- Кэширование на уровне React Query (через Apollo Client) +- Ленивая загрузка изображений +- Обработка ошибок сети \ No newline at end of file diff --git a/src/components/CoreProductCard.tsx b/src/components/CoreProductCard.tsx index 96174f7..dd4a002 100644 --- a/src/components/CoreProductCard.tsx +++ b/src/components/CoreProductCard.tsx @@ -30,6 +30,7 @@ interface CoreProductCardProps { isAnalog?: boolean; isLoadingOffers?: boolean; onLoadOffers?: () => void; + partsIndexPowered?: boolean; } const CoreProductCard: React.FC = ({ @@ -41,7 +42,8 @@ const CoreProductCard: React.FC = ({ showMoreText, isAnalog = false, isLoadingOffers = false, - onLoadOffers + onLoadOffers, + partsIndexPowered = false }) => { const { addItem } = useCart(); const { addToFavorites, removeFromFavorites, isFavorite } = useFavorites(); @@ -196,6 +198,11 @@ const CoreProductCard: React.FC = ({ {image && (
{name} + {partsIndexPowered && ( +
+ powered by Parts Index +
+ )}
)} @@ -246,6 +253,11 @@ const CoreProductCard: React.FC = ({ {image && (
{name} + {partsIndexPowered && ( +
+ powered by Parts Index +
+ )}
)} diff --git a/src/hooks/usePartsIndex.ts b/src/hooks/usePartsIndex.ts index 190c2d8..0a519b1 100644 --- a/src/hooks/usePartsIndex.ts +++ b/src/hooks/usePartsIndex.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { partsIndexService } from '@/lib/partsindex-service'; -import { PartsIndexCatalog, PartsIndexGroup, PartsIndexTabData } from '@/types/partsindex'; +import { PartsIndexCatalog, PartsIndexGroup, PartsIndexTabData, PartsIndexEntityInfo } from '@/types/partsindex'; export const usePartsIndexCatalogs = () => { const [catalogs, setCatalogs] = useState([]); @@ -59,6 +59,44 @@ export const usePartsIndexCatalogGroups = (catalogId: string | null) => { return { group, loading, error }; }; +export const usePartsIndexEntityInfo = (code: string | null, brand?: string | null) => { + const [entityInfo, setEntityInfo] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!code) { + setEntityInfo(null); + return; + } + + const fetchEntityInfo = async () => { + try { + setLoading(true); + setError(null); + const response = await partsIndexService.getEntityInfo(code, brand || undefined, 'ru'); + + // Берем первый элемент из списка, если он есть + if (response.list && response.list.length > 0) { + setEntityInfo(response.list[0]); + } else { + setEntityInfo(null); + } + } catch (err) { + setError(err as Error); + console.error(`Ошибка загрузки информации о детали ${code}:`, err); + setEntityInfo(null); + } finally { + setLoading(false); + } + }; + + fetchEntityInfo(); + }, [code, brand]); + + return { entityInfo, loading, error }; +}; + // Функция для преобразования данных Parts Index в формат меню export const transformPartsIndexToTabData = ( catalogs: PartsIndexCatalog[], diff --git a/src/lib/partsindex-service.ts b/src/lib/partsindex-service.ts index ef11a42..f18df22 100644 --- a/src/lib/partsindex-service.ts +++ b/src/lib/partsindex-service.ts @@ -1,4 +1,4 @@ -import { PartsIndexCatalogsResponse, PartsIndexGroup } from '@/types/partsindex'; +import { PartsIndexCatalogsResponse, PartsIndexGroup, PartsIndexEntityInfoResponse } from '@/types/partsindex'; const PARTS_INDEX_API_BASE = 'https://api.parts-index.com'; const API_KEY = 'PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE'; @@ -54,6 +54,41 @@ class PartsIndexService { throw error; } } + + /** + * Получить информацию о детали по артикулу и бренду + */ + async getEntityInfo(code: string, brand?: string, lang: string = 'ru'): Promise { + try { + const params = new URLSearchParams({ + code: code, + lang: lang + }); + + if (brand) { + params.append('brand', brand); + } + + const response = await fetch( + `${PARTS_INDEX_API_BASE}/v1/entities?${params.toString()}`, + { + headers: { + 'Accept': 'application/json', + 'Authorization': API_KEY, + }, + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error(`Ошибка получения информации о детали ${code}:`, error); + throw error; + } + } } export const partsIndexService = new PartsIndexService(); \ No newline at end of file diff --git a/src/pages/search-result.tsx b/src/pages/search-result.tsx index 09d313c..6707a64 100644 --- a/src/pages/search-result.tsx +++ b/src/pages/search-result.tsx @@ -15,6 +15,7 @@ import CatalogSortDropdown from "@/components/CatalogSortDropdown"; import MobileMenuBottomSection from '../components/MobileMenuBottomSection'; import { SEARCH_PRODUCT_OFFERS, GET_ANALOG_OFFERS } from "@/lib/graphql"; import { useArticleImage } from "@/hooks/useArticleImage"; +import { usePartsIndexEntityInfo } from "@/hooks/usePartsIndex"; const ANALOGS_CHUNK_SIZE = 5; @@ -255,6 +256,12 @@ export default function SearchResult() { }); const { imageUrl: mainImageUrl } = useArticleImage(artId as string, { enabled: !!artId }); + + // Получаем информацию о детали из Parts Index + const { entityInfo, loading: partsIndexLoading } = usePartsIndexEntityInfo( + searchQuery || null, + brandQuery || null + ); const [ getAnalogOffers, @@ -568,15 +575,20 @@ export default function SearchResult() { return null; } + // Используем фотографию из Parts Index, если она есть, иначе fallback на mainImageUrl + const partsIndexImage = entityInfo?.images?.[0]; + const displayImage = partsIndexImage || mainImageUrl; + return ( <> !o.isAnalog).length ? "Показать еще" : undefined} + partsIndexPowered={!!partsIndexImage} /> ); diff --git a/src/types/partsindex.ts b/src/types/partsindex.ts index 904f7c8..d33a0a4 100644 --- a/src/types/partsindex.ts +++ b/src/types/partsindex.ts @@ -152,4 +152,44 @@ export interface PartsIndexParamsVariables { generationId?: string; params?: string; q?: string; +} + +// Типы для получения информации о детали по артикулу +export interface PartsIndexEntityInfo { + id: string; + name: PartsIndexProductName; + originalName: string; + code: string; + barcodes: string[]; + brand: PartsIndexBrand; + description: string; + parameters: { + id: string; + name: string; + params: PartsIndexParameter[]; + }[]; + images: string[]; + links: any[]; + groups: { + main: Array<{ + id: string; + name: string; + level: number; + }>; + additional: Array>; + }; +} + +export interface PartsIndexEntityInfoResponse { + list: PartsIndexEntityInfo[]; +} + +export interface PartsIndexEntityInfoVariables { + code: string; + brand?: string; + lang?: string; } \ No newline at end of file