import React, { useRef, useState } from "react"; import { useQuery } from '@apollo/client'; import { useRouter } from 'next/router'; import { GET_LAXIMO_UNIT_INFO, GET_LAXIMO_UNIT_IMAGE_MAP } from '@/lib/graphql'; import BrandSelectionModal from '../BrandSelectionModal'; interface KnotInProps { catalogCode?: string; vehicleId?: string; ssd?: string; unitId?: string; unitName?: string; parts?: Array<{ detailid?: string; codeonimage?: string | number; oem?: string; name?: string; price?: string | number; brand?: string; availability?: string; note?: string; attributes?: Array<{ key: string; name?: string; value: string }>; }>; onPartSelect?: (codeOnImage: string | number | null) => void; // Коллбек для уведомления KnotParts о выделении детали onPartsHighlight?: (codeOnImage: string | number | null) => void; // Коллбек для подсветки при hover selectedParts?: Set; // Выбранные детали (множественный выбор) } // Функция для корректного формирования URL изображения const getImageUrl = (baseUrl: string, size: string) => { if (!baseUrl) return ''; return baseUrl .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace('%size%', size); }; const KnotIn: React.FC = ({ catalogCode, vehicleId, ssd, unitId, unitName, parts, onPartSelect, onPartsHighlight, selectedParts = new Set() }) => { const imgRef = useRef(null); const [imageScale, setImageScale] = useState({ x: 1, y: 1 }); const selectedImageSize = 'source'; const [isBrandModalOpen, setIsBrandModalOpen] = useState(false); const [selectedDetail, setSelectedDetail] = useState<{ oem: string; name: string } | null>(null); const [hoveredCodeOnImage, setHoveredCodeOnImage] = useState(null); const router = useRouter(); // Получаем инфо об узле (для картинки) console.log('🔍 KnotIn - GET_LAXIMO_UNIT_INFO запрос:', { catalogCode, vehicleId, unitId, ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует', ssdLength: ssd?.length, skipCondition: !catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === '' }); const { data: unitInfoData, loading: unitInfoLoading, error: unitInfoError } = useQuery( GET_LAXIMO_UNIT_INFO, { variables: { catalogCode, vehicleId, unitId, ssd }, skip: !catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === '', errorPolicy: 'all', } ); // Получаем карту координат console.log('🔍 KnotIn - GET_LAXIMO_UNIT_IMAGE_MAP запрос:', { catalogCode, vehicleId, unitId, ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует', ssdLength: ssd?.length, skipCondition: !catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === '' }); const { data: imageMapData, loading: imageMapLoading, error: imageMapError } = useQuery( GET_LAXIMO_UNIT_IMAGE_MAP, { variables: { catalogCode, vehicleId, unitId, ssd }, skip: !catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === '', errorPolicy: 'all', } ); // Если нет необходимых данных, показываем заглушку if (!catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === '') { console.log('⚠️ KnotIn: отсутствуют необходимые данные:', { catalogCode: !!catalogCode, vehicleId: !!vehicleId, unitId: !!unitId, ssd: !!ssd, ssdValid: ssd ? ssd.trim() !== '' : false }); return (
Схема узла
Выберите узел для отображения схемы
{process.env.NODE_ENV === 'development' && (
Debug: catalogCode={catalogCode}, vehicleId={vehicleId}, unitId={unitId}, ssd={ssd ? 'есть' : 'нет'}
)}
); } const unitInfo = unitInfoData?.laximoUnitInfo; const coordinates = imageMapData?.laximoUnitImageMap?.coordinates || []; const imageUrl = unitInfo?.imageurl ? getImageUrl(unitInfo.imageurl, selectedImageSize) : ''; // Логируем успешную загрузку данных React.useEffect(() => { if (unitInfo) { console.log('✅ KnotIn: данные узла загружены:', { unitName: unitInfo.name, hasImage: !!unitInfo.imageurl, imageUrl: unitInfo.imageurl, processedImageUrl: imageUrl }); } }, [unitInfo, imageUrl]); React.useEffect(() => { if (coordinates.length > 0) { console.log('✅ KnotIn: координаты карты загружены:', { coordinatesCount: coordinates.length, firstCoordinate: coordinates[0] }); } else if (imageMapData) { console.log('⚠️ KnotIn: карта изображений загружена, но координаты пустые:', imageMapData); } }, [coordinates, imageMapData]); // Масштабируем точки после загрузки картинки const handleImageLoad = (e: React.SyntheticEvent) => { const img = e.currentTarget; if (!img.naturalWidth || !img.naturalHeight) return; setImageScale({ x: img.offsetWidth / img.naturalWidth, y: img.offsetHeight / img.naturalHeight, }); }; // Обработчик наведения на точку const handlePointHover = (coord: any) => { // Попробуем использовать разные поля для связи const identifierToUse = coord.detailid || coord.codeonimage || coord.code; console.log('🔍 KnotIn - hover на точку:', { coord, detailid: coord.detailid, codeonimage: coord.codeonimage, code: coord.code, identifierToUse, type: typeof identifierToUse, coordinatesLength: coordinates.length, partsLength: parts?.length || 0, firstCoord: coordinates[0], firstPart: parts?.[0] }); setHoveredCodeOnImage(identifierToUse); if (onPartsHighlight) { onPartsHighlight(identifierToUse); } }; // Клик по точке: выделить в списке деталей const handlePointClick = (coord: any) => { if (!parts) return; const identifierToUse = coord.detailid || coord.codeonimage || coord.code; console.log('Клик по точке:', identifierToUse, 'Координата:', coord, 'Все детали:', parts); // Уведомляем родительский компонент о выборе детали для выделения в списке if (onPartSelect) { onPartSelect(identifierToUse); } }; // Двойной клик по точке: переход на страницу выбора бренда const handlePointDoubleClick = (coord: any) => { if (!parts) return; const identifierToUse = coord.detailid || coord.codeonimage || coord.code; console.log('Двойной клик по точке:', identifierToUse, 'Координата:', coord); const part = parts.find( (p) => (p.detailid && p.detailid.toString() === identifierToUse?.toString()) || (p.codeonimage && p.codeonimage.toString() === identifierToUse?.toString()) ); if (part?.oem) { // Переходим на страницу выбора бренда вместо модального окна const url = `/vehicle-search/${catalogCode}/${vehicleId}/part/${part.oem}/brands?detailName=${encodeURIComponent(part.name || '')}`; router.push(url); } else { console.warn('Нет артикула (oem) для выбранной точки:', identifierToUse, part); } }; // Для отладки: вывести детали и координаты React.useEffect(() => { console.log('KnotIn parts:', parts); console.log('KnotIn coordinates:', coordinates); if (coordinates.length > 0) { console.log('🔍 Первые 5 координат:', coordinates.slice(0, 5).map((c: any) => ({ code: c.code, codeonimage: c.codeonimage, detailid: c.detailid, x: c.x, y: c.y }))); } if (parts && parts.length > 0) { console.log('🔍 Первые 5 деталей:', parts.slice(0, 5).map(p => ({ name: p.name, codeonimage: p.codeonimage, detailid: p.detailid, oem: p.oem }))); } // Попытка связать координаты с деталями if (coordinates.length > 0 && parts && parts.length > 0) { console.log('🔗 Попытка связать координаты с деталями:'); coordinates.forEach((coord: any, idx: number) => { const matchingPart = parts.find(part => part.detailid === coord.detailid || part.codeonimage === coord.codeonimage || part.codeonimage === coord.code ); if (matchingPart) { console.log(` ✅ Координата ${idx}: detailid=${coord.detailid}, codeonimage=${coord.codeonimage} -> Деталь: ${matchingPart.name}`); } else { console.log(` ❌ Координата ${idx}: detailid=${coord.detailid}, codeonimage=${coord.codeonimage} -> НЕ НАЙДЕНА`); } }); } }, [parts, coordinates]); if (unitInfoLoading || imageMapLoading) { console.log('🔄 KnotIn: загрузка данных...', { unitInfoLoading, imageMapLoading, unitInfoError: unitInfoError?.message, imageMapError: imageMapError?.message }); return
Загружаем схему узла...
; } if (unitInfoError) { console.error('❌ KnotIn: ошибка загрузки информации об узле:', unitInfoError); return (
Ошибка загрузки схемы: {unitInfoError.message} {process.env.NODE_ENV === 'development' && (
GraphQL Error: {JSON.stringify(unitInfoError, null, 2)}
)}
); } if (imageMapError) { console.error('❌ KnotIn: ошибка загрузки карты изображений:', imageMapError); } if (!imageUrl) { console.log('⚠️ KnotIn: нет URL изображения:', { unitInfo: !!unitInfo, imageurl: unitInfo?.imageurl, unitInfoData: !!unitInfoData }); return (
Нет изображения для этого узла {process.env.NODE_ENV === 'development' && unitInfo && (
Debug: unitInfo.imageurl = {unitInfo.imageurl || 'отсутствует'}
)}
); } return ( <>
{unitName {/* Точки/области */} {coordinates.map((coord: any, idx: number) => { // Кружки всегда 32x32px, центрируем по координате const size = 22; const scaledX = coord.x * imageScale.x - size / 2; const scaledY = coord.y * imageScale.y - size / 2; // Используем code или codeonimage в зависимости от структуры данных const codeValue = coord.code || coord.codeonimage; // Определяем состояние точки const isSelected = selectedParts.has(codeValue); const isHovered = hoveredCodeOnImage === codeValue; // Определяем цвета на основе состояния let backgroundColor = '#B7CAE2'; // Базовый цвет let textColor = '#000'; if (isSelected) { backgroundColor = '#22C55E'; // Зеленый для выбранных textColor = '#fff'; } else if (isHovered) { backgroundColor = '#EC1C24'; // Красный при наведении textColor = '#fff'; } return (
{ if (e.key === 'Enter' || e.key === ' ') handlePointClick(coord); }} className="absolute flex items-center justify-center cursor-pointer transition-all duration-200 ease-in-out" style={{ left: scaledX, top: scaledY, width: size, height: size, backgroundColor, borderRadius: '50%', border: isSelected ? '2px solid #16A34A' : 'none', transform: isHovered || isSelected ? 'scale(1.1)' : 'scale(1)', zIndex: isHovered || isSelected ? 10 : 1, pointerEvents: 'auto', }} title={`${codeValue} (Клик - выделить в списке, двойной клик - перейти к выбору бренда)`} onClick={() => handlePointClick(coord)} onDoubleClick={() => handlePointDoubleClick(coord)} onMouseEnter={() => handlePointHover(coord)} onMouseLeave={() => { setHoveredCodeOnImage(null); if (onPartsHighlight) { onPartsHighlight(null); } }} > {codeValue}
); })}
{/* Модалка выбора бренда */} setIsBrandModalOpen(false)} articleNumber={selectedDetail?.oem || ''} detailName={selectedDetail?.name || ''} /> ); }; export default KnotIn;