Завершение rebase с обновленными компонентами
This commit is contained in:
@ -21,6 +21,9 @@ interface KnotInProps {
|
||||
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<string | number>; // Выбранные детали (множественный выбор)
|
||||
}
|
||||
|
||||
// Функция для корректного формирования URL изображения
|
||||
@ -34,12 +37,23 @@ const getImageUrl = (baseUrl: string, size: string) => {
|
||||
.replace('%size%', size);
|
||||
};
|
||||
|
||||
const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, unitName, parts }) => {
|
||||
const KnotIn: React.FC<KnotInProps> = ({
|
||||
catalogCode,
|
||||
vehicleId,
|
||||
ssd,
|
||||
unitId,
|
||||
unitName,
|
||||
parts,
|
||||
onPartSelect,
|
||||
onPartsHighlight,
|
||||
selectedParts = new Set()
|
||||
}) => {
|
||||
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 [hoveredCodeOnImage, setHoveredCodeOnImage] = useState<string | number | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
// Получаем инфо об узле (для картинки)
|
||||
@ -150,21 +164,62 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
|
||||
});
|
||||
};
|
||||
|
||||
// Клик по точке: найти part по codeonimage/detailid и открыть BrandSelectionModal
|
||||
const handlePointClick = (codeonimage: string | number) => {
|
||||
// Обработчик наведения на точку
|
||||
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;
|
||||
console.log('Клик по точке:', codeonimage, 'Все детали:', parts);
|
||||
|
||||
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.codeonimage && p.codeonimage.toString() === codeonimage.toString()) ||
|
||||
(p.detailid && p.detailid.toString() === codeonimage.toString())
|
||||
(p.detailid && p.detailid.toString() === identifierToUse?.toString()) ||
|
||||
(p.codeonimage && p.codeonimage.toString() === identifierToUse?.toString())
|
||||
);
|
||||
console.log('Найдена деталь для точки:', part);
|
||||
|
||||
if (part?.oem) {
|
||||
setSelectedDetail({ oem: part.oem, name: part.name || '' });
|
||||
setIsBrandModalOpen(true);
|
||||
// Переходим на страницу выбора бренда вместо модального окна
|
||||
const url = `/vehicle-search/${catalogCode}/${vehicleId}/part/${part.oem}/brands?detailName=${encodeURIComponent(part.name || '')}`;
|
||||
router.push(url);
|
||||
} else {
|
||||
console.warn('Нет артикула (oem) для выбранной точки:', codeonimage, part);
|
||||
console.warn('Нет артикула (oem) для выбранной точки:', identifierToUse, part);
|
||||
}
|
||||
};
|
||||
|
||||
@ -172,6 +227,40 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
|
||||
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) {
|
||||
@ -222,11 +311,16 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
|
||||
|
||||
return (
|
||||
<>
|
||||
<<<<<<< HEAD
|
||||
<div className="relative inline-block p-5" style={{ borderRadius: 8, background: '#fff' }}>
|
||||
{/* ВРЕМЕННО: выводим количество точек для быстрой проверки */}
|
||||
{/* <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> */}
|
||||
=======
|
||||
<div className="relative inline-block">
|
||||
|
||||
>>>>>>> c1e0d46 (Добавлены новые функции для выделения и выбора деталей в компонентах UnitDetailsSection и KnotIn. Реализованы обработчики кликов и наведения, а также улучшено взаимодействие с пользователем через подсветку выбранных деталей. Обновлены компоненты KnotParts и VehicleDetailsPage для поддержки множественного выбора деталей и логирования данных от API.)
|
||||
<img
|
||||
ref={imgRef}
|
||||
src={imageUrl}
|
||||
@ -242,38 +336,63 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
|
||||
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 (
|
||||
<div
|
||||
key={`coord-${unitId}-${idx}-${coord.x}-${coord.y}`}
|
||||
tabIndex={0}
|
||||
aria-label={`Деталь ${coord.codeonimage}`}
|
||||
aria-label={`Деталь ${codeValue}`}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter' || e.key === ' ') handlePointClick(coord.codeonimage);
|
||||
if (e.key === 'Enter' || e.key === ' ') handlePointClick(coord);
|
||||
}}
|
||||
className="absolute flex items-center justify-center cursor-pointer transition-colors"
|
||||
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,
|
||||
background: '#B7CAE2',
|
||||
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={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';
|
||||
title={`${codeValue} (Клик - выделить в списке, двойной клик - перейти к выбору бренда)`}
|
||||
onClick={() => handlePointClick(coord)}
|
||||
onDoubleClick={() => handlePointDoubleClick(coord)}
|
||||
onMouseEnter={() => handlePointHover(coord)}
|
||||
onMouseLeave={() => {
|
||||
setHoveredCodeOnImage(null);
|
||||
if (onPartsHighlight) {
|
||||
onPartsHighlight(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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
|
||||
className="flex items-center justify-center w-full h-full text-sm font-bold select-none pointer-events-none transition-colors duration-200"
|
||||
style={{ color: textColor }}
|
||||
>
|
||||
{codeValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
Reference in New Issue
Block a user