293 lines
11 KiB
TypeScript
293 lines
11 KiB
TypeScript
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 }>;
|
||
}>;
|
||
}
|
||
|
||
// Функция для корректного формирования 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<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, unitName, parts }) => {
|
||
const imgRef = useRef<HTMLImageElement>(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 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 (
|
||
<div className="text-center py-8 text-gray-500">
|
||
<div className="text-lg font-medium mb-2">Схема узла</div>
|
||
<div className="text-sm">Выберите узел для отображения схемы</div>
|
||
{process.env.NODE_ENV === 'development' && (
|
||
<div className="text-xs text-red-500 mt-2">
|
||
Debug: catalogCode={catalogCode}, vehicleId={vehicleId}, unitId={unitId}, ssd={ssd ? 'есть' : 'нет'}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
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<HTMLImageElement>) => {
|
||
const img = e.currentTarget;
|
||
if (!img.naturalWidth || !img.naturalHeight) return;
|
||
setImageScale({
|
||
x: img.offsetWidth / img.naturalWidth,
|
||
y: img.offsetHeight / img.naturalHeight,
|
||
});
|
||
};
|
||
|
||
// Клик по точке: найти part по codeonimage/detailid и открыть BrandSelectionModal
|
||
const handlePointClick = (codeonimage: string | number) => {
|
||
if (!parts) return;
|
||
console.log('Клик по точке:', codeonimage, 'Все детали:', parts);
|
||
const part = parts.find(
|
||
(p) =>
|
||
(p.codeonimage && p.codeonimage.toString() === codeonimage.toString()) ||
|
||
(p.detailid && p.detailid.toString() === codeonimage.toString())
|
||
);
|
||
console.log('Найдена деталь для точки:', part);
|
||
if (part?.oem) {
|
||
setSelectedDetail({ oem: part.oem, name: part.name || '' });
|
||
setIsBrandModalOpen(true);
|
||
} else {
|
||
console.warn('Нет артикула (oem) для выбранной точки:', codeonimage, part);
|
||
}
|
||
};
|
||
|
||
// Для отладки: вывести детали и координаты
|
||
React.useEffect(() => {
|
||
console.log('KnotIn parts:', parts);
|
||
console.log('KnotIn coordinates:', coordinates);
|
||
}, [parts, coordinates]);
|
||
|
||
if (unitInfoLoading || imageMapLoading) {
|
||
console.log('🔄 KnotIn: загрузка данных...', {
|
||
unitInfoLoading,
|
||
imageMapLoading,
|
||
unitInfoError: unitInfoError?.message,
|
||
imageMapError: imageMapError?.message
|
||
});
|
||
return <div className="text-center py-8 text-gray-500">Загружаем схему узла...</div>;
|
||
}
|
||
|
||
if (unitInfoError) {
|
||
console.error('❌ KnotIn: ошибка загрузки информации об узле:', unitInfoError);
|
||
return (
|
||
<div className="text-center py-8 text-red-600">
|
||
Ошибка загрузки схемы: {unitInfoError.message}
|
||
{process.env.NODE_ENV === 'development' && (
|
||
<div className="text-xs mt-2 text-gray-500">
|
||
GraphQL Error: {JSON.stringify(unitInfoError, null, 2)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (imageMapError) {
|
||
console.error('❌ KnotIn: ошибка загрузки карты изображений:', imageMapError);
|
||
}
|
||
|
||
if (!imageUrl) {
|
||
console.log('⚠️ KnotIn: нет URL изображения:', {
|
||
unitInfo: !!unitInfo,
|
||
imageurl: unitInfo?.imageurl,
|
||
unitInfoData: !!unitInfoData
|
||
});
|
||
return (
|
||
<div className="text-center py-8 text-gray-400">
|
||
Нет изображения для этого узла
|
||
{process.env.NODE_ENV === 'development' && unitInfo && (
|
||
<div className="text-xs mt-2 text-gray-500">
|
||
Debug: unitInfo.imageurl = {unitInfo.imageurl || 'отсутствует'}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<div className="relative inline-block">
|
||
{/* ВРЕМЕННО: выводим количество точек для быстрой проверки */}
|
||
{/* <div style={{ position: 'absolute', top: 4, left: 4, zIndex: 20, background: 'rgba(255,0,0,0.1)', color: '#c00', fontWeight: 700, fontSize: 14, padding: '2px 8px', borderRadius: 6 }}>
|
||
{coordinates.length} точек
|
||
</div> */}
|
||
<img
|
||
ref={imgRef}
|
||
src={imageUrl}
|
||
loading="lazy"
|
||
alt={unitName || unitInfo?.name || "Изображение узла"}
|
||
onLoad={handleImageLoad}
|
||
className="max-w-full h-auto mx-auto rounded"
|
||
style={{ maxWidth: 400, display: 'block' }}
|
||
/>
|
||
{/* Точки/области */}
|
||
{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;
|
||
return (
|
||
<div
|
||
key={`coord-${unitId}-${idx}-${coord.x}-${coord.y}`}
|
||
tabIndex={0}
|
||
aria-label={`Деталь ${coord.codeonimage}`}
|
||
onKeyDown={e => {
|
||
if (e.key === 'Enter' || e.key === ' ') handlePointClick(coord.codeonimage);
|
||
}}
|
||
className="absolute flex items-center justify-center cursor-pointer transition-colors"
|
||
style={{
|
||
left: scaledX,
|
||
top: scaledY,
|
||
width: size,
|
||
height: size,
|
||
background: '#B7CAE2',
|
||
borderRadius: '50%',
|
||
|
||
pointerEvents: 'auto',
|
||
}}
|
||
title={coord.codeonimage}
|
||
onClick={() => handlePointClick(coord.codeonimage)}
|
||
onMouseEnter={e => {
|
||
(e.currentTarget as HTMLDivElement).style.background = '#EC1C24';
|
||
(e.currentTarget.querySelector('span') as HTMLSpanElement).style.color = '#fff';
|
||
}}
|
||
onMouseLeave={e => {
|
||
(e.currentTarget as HTMLDivElement).style.background = '#B7CAE2';
|
||
(e.currentTarget.querySelector('span') as HTMLSpanElement).style.color = '#000';
|
||
}}
|
||
>
|
||
<span className="flex items-center justify-center w-full h-full text-black text-sm font-bold select-none pointer-events-none" style={{color: '#000'}}>
|
||
{coord.codeonimage}
|
||
</span>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
{/* Модалка выбора бренда */}
|
||
<BrandSelectionModal
|
||
isOpen={isBrandModalOpen}
|
||
onClose={() => setIsBrandModalOpen(false)}
|
||
articleNumber={selectedDetail?.oem || ''}
|
||
detailName={selectedDetail?.name || ''}
|
||
/>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default KnotIn;
|