Добавлено получение навигационных категорий с иконками и обновление логики отображения иконок в компоненте BottomHead. Обновлены типы данных и стили для навигационных иконок. Оптимизирована загрузка групп для категорий в компоненте BottomHeadPartsIndex.
This commit is contained in:
@ -2,8 +2,9 @@ import React, { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { GET_PARTSINDEX_CATEGORIES } from '@/lib/graphql';
|
||||
import { GET_PARTSINDEX_CATEGORIES, GET_NAVIGATION_CATEGORIES } from '@/lib/graphql';
|
||||
import { PartsIndexCatalogsData, PartsIndexCatalogsVariables, PartsIndexCatalog } from '@/types/partsindex';
|
||||
import { NavigationCategory } from '@/types';
|
||||
|
||||
function useIsMobile(breakpoint = 767) {
|
||||
const [isMobile, setIsMobile] = React.useState(false);
|
||||
@ -111,6 +112,22 @@ const transformPartsIndexToTabData = (catalogs: PartsIndexCatalog[]) => {
|
||||
return transformed;
|
||||
};
|
||||
|
||||
// Функция для поиска иконки для категории
|
||||
const findCategoryIcon = (catalogId: string, navigationCategories: NavigationCategory[]): string | null => {
|
||||
console.log('🔍 Ищем иконку для catalogId:', catalogId);
|
||||
console.log('📋 Доступные навигационные категории:', navigationCategories);
|
||||
|
||||
// Ищем навигационную категорию для данного каталога (без группы)
|
||||
const categoryIcon = navigationCategories.find(
|
||||
nav => nav.partsIndexCatalogId === catalogId && (!nav.partsIndexGroupId || nav.partsIndexGroupId === '')
|
||||
);
|
||||
|
||||
console.log('🎯 Найденная категория:', categoryIcon);
|
||||
console.log('🖼️ Возвращаемая иконка:', categoryIcon?.icon || null);
|
||||
|
||||
return categoryIcon?.icon || null;
|
||||
};
|
||||
|
||||
const BottomHead = ({ menuOpen, onClose }: { menuOpen: boolean; onClose: () => void }) => {
|
||||
const isMobile = useIsMobile();
|
||||
const router = useRouter();
|
||||
@ -155,6 +172,20 @@ const BottomHead = ({ menuOpen, onClose }: { menuOpen: boolean; onClose: () => v
|
||||
}
|
||||
);
|
||||
|
||||
// Получаем навигационные категории с иконками
|
||||
const { data: navigationData, loading: navigationLoading, error: navigationError } = useQuery<{ navigationCategories: NavigationCategory[] }>(
|
||||
GET_NAVIGATION_CATEGORIES,
|
||||
{
|
||||
errorPolicy: 'all',
|
||||
onCompleted: (data) => {
|
||||
console.log('🎉 Навигационные категории получены:', data);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('❌ Ошибка загрузки навигационных категорий:', error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Обновляем данные табов когда получаем данные от API
|
||||
useEffect(() => {
|
||||
if (catalogsData?.partsIndexCategoriesWithGroups && catalogsData.partsIndexCategoriesWithGroups.length > 0) {
|
||||
@ -356,27 +387,80 @@ const BottomHead = ({ menuOpen, onClose }: { menuOpen: boolean; onClose: () => v
|
||||
<div className="w-layout-hflex flex-block-90">
|
||||
<div className="w-layout-vflex flex-block-88" style={{ maxHeight: "60vh", overflowY: "auto" }}>
|
||||
{/* Меню с иконками - показываем все категории из API */}
|
||||
{tabData.map((tab, idx) => (
|
||||
<a
|
||||
href="#"
|
||||
className={`link-block-7 w-inline-block${activeTabIndex === idx ? " w--current" : ""}`}
|
||||
key={tab.label}
|
||||
onClick={() => {
|
||||
setActiveTabIndex(idx);
|
||||
}}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<div className="div-block-29">
|
||||
<div className="code-embed-12 w-embed">
|
||||
{/* SVG-звезда */}
|
||||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.3158 0.643914C10.4674 0.365938 10.8666 0.365938 11.0182 0.643914L14.0029 6.11673C14.0604 6.22222 14.1623 6.29626 14.2804 6.31838L20.4077 7.46581C20.7189 7.52409 20.8423 7.9037 20.6247 8.13378L16.3421 12.6636C16.2595 12.7509 16.2206 12.8707 16.2361 12.9899L17.0382 19.1718C17.079 19.4858 16.7561 19.7204 16.47 19.5847L10.8385 16.9114C10.73 16.8599 10.604 16.8599 10.4955 16.9114L4.86394 19.5847C4.5779 19.7204 4.25499 19.4858 4.29573 19.1718L5.0979 12.9899C5.11336 12.8707 5.07444 12.7509 4.99189 12.6636L0.709252 8.13378C0.491728 7.9037 0.615069 7.52409 0.926288 7.46581L7.05357 6.31838C7.17168 6.29626 7.27358 6.22222 7.33112 6.11673L10.3158 0.643914Z" fill="CurrentColor"></path>
|
||||
</svg>
|
||||
{tabData.map((tab, idx) => {
|
||||
// Получаем catalogId для поиска иконки
|
||||
const catalogId = catalogsData?.partsIndexCategoriesWithGroups?.[idx]?.id || `fallback_${idx}`;
|
||||
console.log(`🏷️ Обрабатываем категорию ${idx}: "${tab.label}" с catalogId: "${catalogId}"`);
|
||||
const icon = navigationData?.navigationCategories ? findCategoryIcon(catalogId, navigationData.navigationCategories) : null;
|
||||
console.log(`🎨 Для категории "${tab.label}" будет показана ${icon ? 'иконка: ' + icon : 'звездочка (fallback)'}`);
|
||||
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
className={`link-block-7 w-inline-block${activeTabIndex === idx ? " w--current" : ""}`}
|
||||
key={tab.label}
|
||||
onClick={() => {
|
||||
setActiveTabIndex(idx);
|
||||
}}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<div className="div-block-29">
|
||||
<div className="code-embed-12 w-embed">
|
||||
{(() => {
|
||||
console.log(`🎭 Рендеринг иконки для "${tab.label}": ${icon ? 'IMG' : 'SVG'}`);
|
||||
console.log(`🔍 icon значение:`, icon);
|
||||
return null;
|
||||
})()}
|
||||
|
||||
{/* Условный рендеринг: либо IMG, либо SVG */}
|
||||
{icon ? (
|
||||
<img
|
||||
src={icon}
|
||||
alt={tab.label}
|
||||
width="21"
|
||||
height="20"
|
||||
className="navigation-icon-img"
|
||||
style={{
|
||||
display: 'block',
|
||||
position: 'relative',
|
||||
zIndex: 10,
|
||||
border: '3px solid lime',
|
||||
backgroundColor: 'magenta',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
onLoad={(e) => {
|
||||
console.log('✅ Иконка успешно загружена:', icon);
|
||||
console.log('✅ Элемент img:', e.currentTarget);
|
||||
}}
|
||||
onError={(e) => {
|
||||
console.log('❌ Ошибка загрузки иконки:', icon);
|
||||
console.log('❌ Элемент img с ошибкой:', e.currentTarget);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
/* SVG-звезда как fallback */
|
||||
<svg
|
||||
width="21"
|
||||
height="20"
|
||||
viewBox="0 0 21 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{
|
||||
display: 'block',
|
||||
position: 'relative',
|
||||
zIndex: 5,
|
||||
border: '2px solid blue' // Временно для отладки
|
||||
}}
|
||||
>
|
||||
<path d="M10.3158 0.643914C10.4674 0.365938 10.8666 0.365938 11.0182 0.643914L14.0029 6.11673C14.0604 6.22222 14.1623 6.29626 14.2804 6.31838L20.4077 7.46581C20.7189 7.52409 20.8423 7.9037 20.6247 8.13378L16.3421 12.6636C16.2595 12.7509 16.2206 12.8707 16.2361 12.9899L17.0382 19.1718C17.079 19.4858 16.7561 19.7204 16.47 19.5847L10.8385 16.9114C10.73 16.8599 10.604 16.8599 10.4955 16.9114L4.86394 19.5847C4.5779 19.7204 4.25499 19.4858 4.29573 19.1718L5.0979 12.9899C5.11336 12.8707 5.07444 12.7509 4.99189 12.6636L0.709252 8.13378C0.491728 7.9037 0.615069 7.52409 0.926288 7.46581L7.05357 6.31838C7.17168 6.29626 7.27358 6.22222 7.33112 6.11673L10.3158 0.643914Z" fill="CurrentColor"></path>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-block-47">{tab.label}</div>
|
||||
</a>
|
||||
))}
|
||||
<div className="text-block-47">{tab.label}</div>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Правая часть меню с подкатегориями и картинками */}
|
||||
<div className="w-layout-vflex flex-block-89">
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useQuery, useLazyQuery } from '@apollo/client';
|
||||
import { GET_PARTSINDEX_CATEGORIES } from '@/lib/graphql';
|
||||
|
||||
function useIsMobile(breakpoint = 767) {
|
||||
const [isMobile, setIsMobile] = React.useState(false);
|
||||
@ -13,35 +14,38 @@ function useIsMobile(breakpoint = 767) {
|
||||
return isMobile;
|
||||
}
|
||||
|
||||
// Типы для Parts Index API
|
||||
interface PartsIndexCatalog {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface PartsIndexEntityName {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface PartsIndexGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
lang: string;
|
||||
image: string;
|
||||
lft: number;
|
||||
rgt: number;
|
||||
entityNames: PartsIndexEntityName[];
|
||||
subgroups: PartsIndexGroup[];
|
||||
}
|
||||
|
||||
// Типы данных
|
||||
interface PartsIndexTabData {
|
||||
label: string;
|
||||
heading: string;
|
||||
links: string[];
|
||||
catalogId: string;
|
||||
group?: PartsIndexGroup;
|
||||
group?: any;
|
||||
groupsLoaded?: boolean; // флаг что группы загружены
|
||||
}
|
||||
|
||||
interface PartsIndexCatalog {
|
||||
id: string;
|
||||
name: string;
|
||||
image?: string;
|
||||
groups?: PartsIndexGroup[];
|
||||
}
|
||||
|
||||
interface PartsIndexGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
image?: string;
|
||||
entityNames?: { id: string; name: string }[];
|
||||
subgroups?: { id: string; name: string }[];
|
||||
}
|
||||
|
||||
// GraphQL типы
|
||||
interface PartsIndexCatalogsData {
|
||||
partsIndexCategoriesWithGroups: PartsIndexCatalog[];
|
||||
}
|
||||
|
||||
interface PartsIndexCatalogsVariables {
|
||||
lang?: 'ru' | 'en';
|
||||
}
|
||||
|
||||
// Fallback статичные данные
|
||||
@ -51,57 +55,66 @@ const fallbackTabData: PartsIndexTabData[] = [
|
||||
heading: "Детали ТО",
|
||||
catalogId: "parts_to",
|
||||
links: ["Детали ТО"],
|
||||
groupsLoaded: false,
|
||||
},
|
||||
{
|
||||
label: "Масла",
|
||||
heading: "Масла",
|
||||
catalogId: "oils",
|
||||
links: ["Масла"],
|
||||
groupsLoaded: false,
|
||||
},
|
||||
{
|
||||
label: "Шины",
|
||||
heading: "Шины",
|
||||
catalogId: "tyres",
|
||||
links: ["Шины"],
|
||||
groupsLoaded: false,
|
||||
},
|
||||
];
|
||||
|
||||
// Сервис для работы с Parts Index API
|
||||
const PARTS_INDEX_API_BASE = 'https://api.parts-index.com';
|
||||
const API_KEY = 'PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE';
|
||||
// Создаем базовые табы только с названиями каталогов
|
||||
const createBaseTabData = (catalogs: PartsIndexCatalog[]): PartsIndexTabData[] => {
|
||||
console.log('🔄 Создаем базовые табы из каталогов:', catalogs.length, 'элементов');
|
||||
|
||||
return catalogs.map(catalog => ({
|
||||
label: catalog.name,
|
||||
heading: catalog.name,
|
||||
links: [catalog.name], // Изначально показываем только название каталога
|
||||
catalogId: catalog.id,
|
||||
groupsLoaded: false, // Группы еще не загружены
|
||||
}));
|
||||
};
|
||||
|
||||
async function fetchCatalogs(): Promise<PartsIndexCatalog[]> {
|
||||
try {
|
||||
const response = await fetch(`${PARTS_INDEX_API_BASE}/v1/catalogs?lang=ru`, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
const data = await response.json();
|
||||
return data.list;
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения каталогов Parts Index:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCatalogGroup(catalogId: string): Promise<PartsIndexGroup | null> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${PARTS_INDEX_API_BASE}/v1/catalogs/${catalogId}/groups?lang=ru`,
|
||||
{
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': API_KEY,
|
||||
},
|
||||
// Преобразуем данные PartsIndex в формат нашего меню с группами
|
||||
const transformPartsIndexToTabData = (catalog: PartsIndexCatalog): string[] => {
|
||||
console.log(`📝 Обрабатываем группы каталога: "${catalog.name}"`);
|
||||
|
||||
let links: string[] = [];
|
||||
|
||||
if (catalog.groups && catalog.groups.length > 0) {
|
||||
// Для каждой группы проверяем есть ли подгруппы
|
||||
catalog.groups.forEach(group => {
|
||||
if (group.subgroups && group.subgroups.length > 0) {
|
||||
// Если есть подгруппы, добавляем их названия
|
||||
links.push(...group.subgroups.slice(0, 9 - links.length).map(subgroup => subgroup.name));
|
||||
} else {
|
||||
// Если подгрупп нет, добавляем название самой группы
|
||||
if (links.length < 9) {
|
||||
links.push(group.name);
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Ошибка получения группы каталога ${catalogId}:`, error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Если подкатегорий нет, показываем название категории
|
||||
if (links.length === 0) {
|
||||
links = [catalog.name];
|
||||
}
|
||||
|
||||
console.log(`🔗 Подкатегории для "${catalog.name}":`, links);
|
||||
return links.slice(0, 9); // Ограничиваем максимум 9 элементов
|
||||
};
|
||||
|
||||
const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClose: () => void }) => {
|
||||
const isMobile = useIsMobile();
|
||||
@ -109,7 +122,7 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
|
||||
const [mobileCategory, setMobileCategory] = useState<null | any>(null);
|
||||
const [tabData, setTabData] = useState<PartsIndexTabData[]>(fallbackTabData);
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingGroups, setLoadingGroups] = useState<Set<number>>(new Set());
|
||||
|
||||
// Пагинация категорий
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
@ -126,52 +139,116 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
|
||||
}
|
||||
}, [menuOpen]);
|
||||
|
||||
// Загрузка каталогов и их групп
|
||||
// Получаем только каталоги PartsIndex (без групп для начальной загрузки)
|
||||
const { data: catalogsData, loading, error } = useQuery<PartsIndexCatalogsData, PartsIndexCatalogsVariables>(
|
||||
GET_PARTSINDEX_CATEGORIES,
|
||||
{
|
||||
variables: {
|
||||
lang: 'ru'
|
||||
},
|
||||
errorPolicy: 'all',
|
||||
fetchPolicy: 'cache-first', // Используем кэширование агрессивно
|
||||
nextFetchPolicy: 'cache-first', // Продолжаем использовать кэш
|
||||
notifyOnNetworkStatusChange: false,
|
||||
onCompleted: (data) => {
|
||||
console.log('🎉 PartsIndex каталоги получены через GraphQL (базовые):', data);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('❌ Ошибка загрузки PartsIndex каталогов:', error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Ленивый запрос для загрузки групп конкретного каталога
|
||||
const [loadCatalogGroups, { loading: groupsLoading }] = useLazyQuery<PartsIndexCatalogsData, PartsIndexCatalogsVariables>(
|
||||
GET_PARTSINDEX_CATEGORIES,
|
||||
{
|
||||
errorPolicy: 'all',
|
||||
fetchPolicy: 'cache-first',
|
||||
nextFetchPolicy: 'cache-first',
|
||||
notifyOnNetworkStatusChange: false,
|
||||
onCompleted: (data) => {
|
||||
console.log('🎉 Группы каталога загружены:', data);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('❌ Ошибка загрузки групп каталога:', error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Обновляем базовые данные табов когда получаем каталоги
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
if (tabData === fallbackTabData) { // Загружаем только если еще не загружали
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('🔄 Загружаем каталоги Parts Index...');
|
||||
const catalogs = await fetchCatalogs();
|
||||
if (catalogsData?.partsIndexCategoriesWithGroups && catalogsData.partsIndexCategoriesWithGroups.length > 0) {
|
||||
console.log('✅ Обновляем базовое меню PartsIndex:', catalogsData.partsIndexCategoriesWithGroups.length, 'каталогов');
|
||||
|
||||
const baseTabData = createBaseTabData(catalogsData.partsIndexCategoriesWithGroups);
|
||||
setTabData(baseTabData);
|
||||
setActiveTabIndex(0);
|
||||
} else if (error) {
|
||||
console.warn('⚠️ Используем fallback данные из-за ошибки PartsIndex:', error);
|
||||
setTabData(fallbackTabData);
|
||||
setActiveTabIndex(0);
|
||||
}
|
||||
}, [catalogsData, error]);
|
||||
|
||||
// Функция для ленивой загрузки групп при наведении на таб
|
||||
const loadGroupsForTab = async (tabIndex: number) => {
|
||||
const tab = tabData[tabIndex];
|
||||
if (!tab || tab.groupsLoaded || loadingGroups.has(tabIndex)) {
|
||||
return; // Группы уже загружены или загружаются
|
||||
}
|
||||
|
||||
console.log('🔄 Загружаем группы для каталога:', tab.catalogId);
|
||||
setLoadingGroups(prev => new Set([...prev, tabIndex]));
|
||||
|
||||
try {
|
||||
const result = await loadCatalogGroups({
|
||||
variables: {
|
||||
lang: 'ru'
|
||||
}
|
||||
});
|
||||
|
||||
if (result.data?.partsIndexCategoriesWithGroups) {
|
||||
const catalog = result.data.partsIndexCategoriesWithGroups.find(c => c.id === tab.catalogId);
|
||||
if (catalog) {
|
||||
const links = transformPartsIndexToTabData(catalog);
|
||||
|
||||
if (catalogs.length > 0) {
|
||||
console.log(`✅ Получено ${catalogs.length} каталогов`);
|
||||
|
||||
// Загружаем группы для первых нескольких каталогов
|
||||
const catalogsToLoad = catalogs.slice(0, 10);
|
||||
const tabDataPromises = catalogsToLoad.map(async (catalog) => {
|
||||
const group = await fetchCatalogGroup(catalog.id);
|
||||
|
||||
// Получаем подкатегории из entityNames или повторяем название категории
|
||||
const links = group?.entityNames && group.entityNames.length > 0
|
||||
? group.entityNames.slice(0, 9).map(entity => entity.name)
|
||||
: [catalog.name]; // Если нет подкатегорий, повторяем название категории
|
||||
|
||||
return {
|
||||
label: catalog.name,
|
||||
heading: catalog.name,
|
||||
links,
|
||||
catalogId: catalog.id,
|
||||
group
|
||||
};
|
||||
});
|
||||
|
||||
const apiTabData = await Promise.all(tabDataPromises);
|
||||
console.log('✅ Данные обновлены:', apiTabData.length, 'категорий');
|
||||
setTabData(apiTabData as PartsIndexTabData[]);
|
||||
setActiveTabIndex(0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки данных Parts Index:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// Обновляем конкретный таб с загруженными группами
|
||||
setTabData(prevTabs => {
|
||||
const newTabs = [...prevTabs];
|
||||
newTabs[tabIndex] = {
|
||||
...newTabs[tabIndex],
|
||||
links,
|
||||
group: catalog.groups?.[0],
|
||||
groupsLoaded: true
|
||||
};
|
||||
return newTabs;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки групп для каталога:', tab.catalogId, error);
|
||||
} finally {
|
||||
setLoadingGroups(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(tabIndex);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, []);
|
||||
// Обработчик наведения на таб - загружаем группы
|
||||
const handleTabHover = (tabIndex: number) => {
|
||||
loadGroupsForTab(tabIndex);
|
||||
};
|
||||
|
||||
// Обработчик клика на таб
|
||||
const handleTabClick = (tabIndex: number) => {
|
||||
setActiveTabIndex(tabIndex);
|
||||
|
||||
// Загружаем группы если еще не загружены
|
||||
loadGroupsForTab(tabIndex);
|
||||
};
|
||||
|
||||
// Обработка клика по категории для перехода в каталог
|
||||
const handleCategoryClick = (catalogId: string, categoryName: string, entityId?: string) => {
|
||||
@ -184,7 +261,7 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
|
||||
query: {
|
||||
partsIndexCatalog: catalogId,
|
||||
categoryName: encodeURIComponent(categoryName),
|
||||
...(entityId && { entityId })
|
||||
...(entityId && { partsIndexCategory: entityId })
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -294,6 +371,12 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
|
||||
className="mobile-subcategory"
|
||||
key={cat.catalogId}
|
||||
onClick={() => {
|
||||
// Загружаем группы для категории если нужно
|
||||
const catIndex = tabData.findIndex(tab => tab.catalogId === cat.catalogId);
|
||||
if (catIndex !== -1) {
|
||||
loadGroupsForTab(catIndex);
|
||||
}
|
||||
|
||||
const categoryWithData = {
|
||||
...cat,
|
||||
catalogId: cat.catalogId,
|
||||
@ -304,6 +387,9 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
{cat.label}
|
||||
{loadingGroups.has(tabData.findIndex(tab => tab.catalogId === cat.catalogId)) && (
|
||||
<span className="text-xs text-gray-500 ml-2">(загрузка...)</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@ -367,9 +453,8 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
|
||||
href="#"
|
||||
className={`link-block-7 w-inline-block${activeTabIndex === idx ? " w--current" : ""}`}
|
||||
key={tab.catalogId}
|
||||
onClick={() => {
|
||||
setActiveTabIndex(idx);
|
||||
}}
|
||||
onClick={() => handleTabClick(idx)}
|
||||
onMouseEnter={() => handleTabHover(idx)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<div className="div-block-29">
|
||||
@ -388,6 +473,7 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
|
||||
<h3 className="heading-16">
|
||||
{currentPageCategories[activeTabIndex]?.heading || currentPageCategories[0]?.heading}
|
||||
{loading && <span className="text-sm text-gray-500 ml-2">(обновление...)</span>}
|
||||
{loadingGroups.has(activeTabIndex) && <span className="text-sm text-gray-500 ml-2">(загрузка групп...)</span>}
|
||||
</h3>
|
||||
<div className="w-layout-hflex flex-block-92">
|
||||
<div className="w-layout-vflex flex-block-91">
|
||||
|
@ -373,6 +373,8 @@ export const CREATE_PAYMENT = gql`
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
|
||||
export const GET_ORDERS = gql`
|
||||
query GetOrders($clientId: String, $status: OrderStatus, $search: String, $limit: Int, $offset: Int) {
|
||||
orders(clientId: $clientId, status: $status, search: $search, limit: $limit, offset: $offset) {
|
||||
@ -1367,6 +1369,23 @@ export const GET_PARTSINDEX_CATEGORIES = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
// Навигационные категории с иконками
|
||||
export const GET_NAVIGATION_CATEGORIES = gql`
|
||||
query GetNavigationCategories {
|
||||
navigationCategories {
|
||||
id
|
||||
partsIndexCatalogId
|
||||
partsIndexGroupId
|
||||
name
|
||||
catalogName
|
||||
groupName
|
||||
icon
|
||||
sortOrder
|
||||
isHidden
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Новый запрос для получения товаров каталога PartsIndex
|
||||
export const GET_PARTSINDEX_CATALOG_ENTITIES = gql`
|
||||
query GetPartsIndexCatalogEntities(
|
||||
|
@ -138,7 +138,7 @@ export default function Catalog() {
|
||||
limit: ITEMS_PER_PAGE,
|
||||
page: partsIndexPage,
|
||||
q: searchQuery || undefined,
|
||||
params: Object.keys(selectedFilters).length > 0 ? JSON.stringify(selectedFilters) : undefined
|
||||
params: undefined // Будем обновлять через refetch
|
||||
},
|
||||
skip: !isPartsIndexMode || !groupId, // Пропускаем запрос если нет groupId
|
||||
fetchPolicy: 'cache-and-network'
|
||||
@ -146,7 +146,7 @@ export default function Catalog() {
|
||||
);
|
||||
|
||||
// Загружаем параметры фильтрации для PartsIndex
|
||||
const { data: paramsData, loading: paramsLoading, error: paramsError } = useQuery<PartsIndexParamsData, PartsIndexParamsVariables>(
|
||||
const { data: paramsData, loading: paramsLoading, error: paramsError, refetch: refetchParams } = useQuery<PartsIndexParamsData, PartsIndexParamsVariables>(
|
||||
GET_PARTSINDEX_CATALOG_PARAMS,
|
||||
{
|
||||
variables: {
|
||||
@ -154,7 +154,7 @@ export default function Catalog() {
|
||||
groupId: groupId as string,
|
||||
lang: 'ru',
|
||||
q: searchQuery || undefined,
|
||||
params: Object.keys(selectedFilters).length > 0 ? JSON.stringify(selectedFilters) : undefined
|
||||
params: undefined // Будем обновлять через refetch
|
||||
},
|
||||
skip: !isPartsIndexMode || !groupId, // Пропускаем запрос если нет groupId
|
||||
fetchPolicy: 'cache-first'
|
||||
@ -215,18 +215,44 @@ export default function Catalog() {
|
||||
}
|
||||
}, [entitiesData]);
|
||||
|
||||
// Преобразование выбранных фильтров в формат PartsIndex API
|
||||
const convertFiltersToPartsIndexParams = useCallback((): Record<string, any> => {
|
||||
if (!paramsData?.partsIndexCatalogParams?.list || Object.keys(selectedFilters).length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const apiParams: Record<string, any> = {};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return apiParams;
|
||||
}, [paramsData, selectedFilters]);
|
||||
|
||||
// Генерация фильтров для PartsIndex на основе параметров API
|
||||
const generatePartsIndexFilters = useCallback((): FilterConfig[] => {
|
||||
if (!paramsData?.partsIndexCatalogParams?.list) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return paramsData.partsIndexCatalogParams.list.map(param => {
|
||||
return paramsData.partsIndexCatalogParams.list.map((param: any) => {
|
||||
if (param.type === 'range') {
|
||||
// Для range фильтров ищем min и max значения
|
||||
const numericValues = param.values
|
||||
.map(v => parseFloat(v.value))
|
||||
.filter(v => !isNaN(v));
|
||||
.map((v: any) => parseFloat(v.value))
|
||||
.filter((v: number) => !isNaN(v));
|
||||
|
||||
const min = numericValues.length > 0 ? Math.min(...numericValues) : 0;
|
||||
const max = numericValues.length > 0 ? Math.max(...numericValues) : 100;
|
||||
@ -243,8 +269,8 @@ export default function Catalog() {
|
||||
type: 'dropdown' as const,
|
||||
title: param.name,
|
||||
options: param.values
|
||||
.filter(value => value.available) // Показываем только доступные
|
||||
.map(value => value.title || value.value),
|
||||
.filter((value: any) => value.available) // Показываем только доступные
|
||||
.map((value: any) => value.title || value.value),
|
||||
multi: true,
|
||||
showAll: true
|
||||
};
|
||||
@ -252,6 +278,8 @@ export default function Catalog() {
|
||||
});
|
||||
}, [paramsData]);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (isPartsIndexMode) {
|
||||
// Для PartsIndex генерируем фильтры на основе параметров API
|
||||
@ -426,16 +454,38 @@ export default function Catalog() {
|
||||
// При изменении поиска или фильтров сбрасываем пагинацию
|
||||
setShowEmptyState(false);
|
||||
|
||||
// Если изменился поисковый запрос, нужно перезагрузить данные с сервера
|
||||
// Если изменился поисковый запрос или фильтры, нужно перезагрузить данные с сервера
|
||||
if (searchQuery.trim() || Object.keys(selectedFilters).length > 0) {
|
||||
console.log('🔍 Поисковый запрос или фильтры изменились, сбрасываем пагинацию');
|
||||
setPartsIndexPage(1);
|
||||
setHasMoreEntities(true);
|
||||
// refetch будет автоматически вызван при изменении partsIndexPage
|
||||
|
||||
// Перезагружаем данные с новыми параметрами фильтрации
|
||||
const apiParams = convertFiltersToPartsIndexParams();
|
||||
const paramsString = Object.keys(apiParams).length > 0 ? JSON.stringify(apiParams) : undefined;
|
||||
|
||||
// Также обновляем параметры фильтрации
|
||||
refetchParams({
|
||||
catalogId: catalogId as string,
|
||||
groupId: groupId as string,
|
||||
lang: 'ru',
|
||||
q: searchQuery || undefined,
|
||||
params: paramsString
|
||||
});
|
||||
|
||||
refetchEntities({
|
||||
catalogId: catalogId as string,
|
||||
groupId: groupId as string,
|
||||
lang: 'ru',
|
||||
limit: ITEMS_PER_PAGE,
|
||||
page: 1,
|
||||
q: searchQuery || undefined,
|
||||
params: paramsString
|
||||
});
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isPartsIndexMode, searchQuery, JSON.stringify(selectedFilters)]);
|
||||
}, [isPartsIndexMode, searchQuery, JSON.stringify(selectedFilters), refetchEntities, refetchParams, convertFiltersToPartsIndexParams]);
|
||||
|
||||
// Управляем показом пустого состояния с задержкой
|
||||
useEffect(() => {
|
||||
|
@ -377,4 +377,23 @@ button,
|
||||
.tooltip-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Стили для навигационных иконок */
|
||||
.navigation-icon-img {
|
||||
display: block !important;
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
z-index: 9999 !important;
|
||||
max-width: 21px !important;
|
||||
max-height: 20px !important;
|
||||
width: 21px !important;
|
||||
height: 20px !important;
|
||||
object-fit: contain !important;
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
border: 2px solid red !important; /* Временно для отладки */
|
||||
background-color: yellow !important; /* Временно для отладки */
|
||||
pointer-events: none !important; /* Чтобы не блокировать клики */
|
||||
}
|
12
src/types/index.ts
Normal file
12
src/types/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// Навигационные категории
|
||||
export interface NavigationCategory {
|
||||
id: string
|
||||
partsIndexCatalogId: string
|
||||
partsIndexGroupId: string | null
|
||||
name: string
|
||||
catalogName: string
|
||||
groupName: string | null
|
||||
icon: string | null
|
||||
sortOrder: number
|
||||
isHidden: boolean
|
||||
}
|
Reference in New Issue
Block a user