import React, { useState, useEffect } from "react"; 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); React.useEffect(() => { const check = () => setIsMobile(window.innerWidth <= breakpoint); check(); window.addEventListener("resize", check); return () => window.removeEventListener("resize", check); }, [breakpoint]); return isMobile; } // Типы данных interface PartsIndexTabData { label: string; heading: string; links: string[]; catalogId: string; 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 статичные данные const fallbackTabData: PartsIndexTabData[] = [ { label: "Детали ТО", heading: "Детали ТО", catalogId: "parts_to", links: ["Детали ТО"], groupsLoaded: false, }, { label: "Масла", heading: "Масла", catalogId: "oils", links: ["Масла"], groupsLoaded: false, }, { label: "Шины", heading: "Шины", catalogId: "tyres", links: ["Шины"], groupsLoaded: false, }, ]; // Создаем базовые табы только с названиями каталогов 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, // Группы еще не загружены })); }; // Преобразуем данные 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 (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(); const router = useRouter(); const [mobileCategory, setMobileCategory] = useState(null); const [tabData, setTabData] = useState(fallbackTabData); const [activeTabIndex, setActiveTabIndex] = useState(0); const [loadingGroups, setLoadingGroups] = useState>(new Set()); // Пагинация категорий const [currentPage, setCurrentPage] = useState(0); const categoriesPerPage = 6; // Количество категорий на странице // --- Overlay animation state --- const [showOverlay, setShowOverlay] = useState(false); useEffect(() => { if (menuOpen) { setShowOverlay(true); } else { const timeout = setTimeout(() => setShowOverlay(false), 300); return () => clearTimeout(timeout); } }, [menuOpen]); // Получаем только каталоги PartsIndex (без групп для начальной загрузки) const { data: catalogsData, loading, error } = useQuery( 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( GET_PARTSINDEX_CATEGORIES, { errorPolicy: 'all', fetchPolicy: 'cache-first', nextFetchPolicy: 'cache-first', notifyOnNetworkStatusChange: false, onCompleted: (data) => { console.log('🎉 Группы каталога загружены:', data); }, onError: (error) => { console.error('❌ Ошибка загрузки групп каталога:', error); } } ); // Обновляем базовые данные табов когда получаем каталоги useEffect(() => { 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); // Обновляем конкретный таб с загруженными группами 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; }); } }; // Обработчик наведения на таб - загружаем группы const handleTabHover = (tabIndex: number) => { loadGroupsForTab(tabIndex); }; // Обработчик клика на таб const handleTabClick = (tabIndex: number) => { setActiveTabIndex(tabIndex); // Загружаем группы если еще не загружены loadGroupsForTab(tabIndex); }; // Обработка клика по категории для перехода в каталог const handleCategoryClick = (catalogId: string, categoryName: string, entityId?: string) => { console.log('🔍 Клик по категории Parts Index:', { catalogId, categoryName, entityId }); onClose(); router.push({ pathname: '/catalog', query: { partsIndexCatalog: catalogId, categoryName: encodeURIComponent(categoryName), ...(entityId && { partsIndexCategory: entityId }) } }); }; // Получаем текущие категории для отображения с пагинацией const getCurrentPageCategories = () => { const startIndex = currentPage * categoriesPerPage; const endIndex = startIndex + categoriesPerPage; return tabData.slice(startIndex, endIndex); }; // Проверяем, есть ли следующая/предыдущая страница const hasNextPage = (currentPage + 1) * categoriesPerPage < tabData.length; const hasPrevPage = currentPage > 0; // Обработчики пагинации const handleNextPage = () => { if (hasNextPage) { setCurrentPage(prev => prev + 1); setActiveTabIndex(0); } }; const handlePrevPage = () => { if (hasPrevPage) { setCurrentPage(prev => prev - 1); setActiveTabIndex(0); } }; const currentPageCategories = getCurrentPageCategories(); // Только мобильный UX if (isMobile && menuOpen) { return ( <> {showOverlay && (
)} {/* Экран подкатегорий */} {mobileCategory ? (
{mobileCategory.label}
{mobileCategory.links.map((link: string, index: number) => (
{ const entityId = mobileCategory.group?.entityNames?.[index]?.id; handleCategoryClick(mobileCategory.catalogId, link, entityId); }} > {link}
))}
) : ( // Экран выбора категории
Категории Parts Index {loading && (загрузка...)}
{/* Пагинация для мобильной версии */} {tabData.length > categoriesPerPage && (
{currentPage + 1} из {Math.ceil(tabData.length / categoriesPerPage)}
)}
{currentPageCategories.map((cat) => (
{ // Загружаем группы для категории если нужно const catIndex = tabData.findIndex(tab => tab.catalogId === cat.catalogId); if (catIndex !== -1) { loadGroupsForTab(catIndex); } const categoryWithData = { ...cat, catalogId: cat.catalogId, group: cat.group }; setMobileCategory(categoryWithData); }} style={{ cursor: "pointer" }} > {cat.label} {loadingGroups.has(tabData.findIndex(tab => tab.catalogId === cat.catalogId)) && ( (загрузка...) )}
))}
)} ); } // Если не мобильный или меню закрыто, возвращаем пустой элемент if (!menuOpen) { return null; } // Desktop версия return ( <> {showOverlay && (
)}
{/* Кнопки пагинации */} {tabData.length > categoriesPerPage && (
{currentPage + 1} / {Math.ceil(tabData.length / categoriesPerPage)}
)} {/* Меню с иконками - показываем текущую страницу категорий */} {currentPageCategories.map((tab, idx) => ( handleTabClick(idx)} onMouseEnter={() => handleTabHover(idx)} style={{ cursor: "pointer" }} >
{tab.label}
))}
{/* Правая часть меню с подкатегориями и картинками */}

{currentPageCategories[activeTabIndex]?.heading || currentPageCategories[0]?.heading} {loading && (обновление...)} {loadingGroups.has(activeTabIndex) && (загрузка групп...)}

{(currentPageCategories[activeTabIndex]?.links || currentPageCategories[0]?.links || []).map((link, index) => { const activeCategory = currentPageCategories[activeTabIndex] || currentPageCategories[0]; const entityId = activeCategory?.group?.entityNames?.[index]?.id; return (
handleCategoryClick(activeCategory.catalogId, link, entityId)} style={{ cursor: "pointer" }} > {link}
); })}
{/* Табы */}
Parts Index API
Каталоги ТО
Каталоги запчастей
Все каталоги
); }; export default BottomHeadPartsIndex;