Merge pull request 'checkbox' (#6) from numbers into main
Reviewed-on: #6
This commit is contained in:
@ -52,7 +52,7 @@ const BrandSelectionModal: React.FC<BrandSelectionModalProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
|
||||
className="fixed inset-0 bg-black/10 bg-opacity-50 flex items-center justify-center z-50 p-4"
|
||||
onClick={handleBackdropClick}
|
||||
>
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[80vh] overflow-hidden">
|
||||
|
@ -210,7 +210,7 @@ const VehiclePartsSearchSection: React.FC<VehiclePartsSearchSectionProps> = ({
|
||||
<div className="min-h-[400px]">
|
||||
{searchType === 'quickgroups' && supportsQuickGroups && (
|
||||
<QuickGroupsSection
|
||||
catalogCode={vehicleInfo.catalog}
|
||||
catalogCode={vehicleInfo.catalog}
|
||||
vehicleId={vehicleInfo.vehicleid}
|
||||
ssd={vehicleInfo.ssd}
|
||||
/>
|
||||
|
@ -1,4 +1,27 @@
|
||||
import React from "react";
|
||||
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) => {
|
||||
@ -11,25 +34,138 @@ const getImageUrl = (baseUrl: string, size: string) => {
|
||||
.replace('%size%', size);
|
||||
};
|
||||
|
||||
const KnotIn = ({ node }: { node: any }) => {
|
||||
if (!node) return null;
|
||||
let imageUrl = '';
|
||||
if (node.imageurl) {
|
||||
imageUrl = getImageUrl(node.imageurl, '250');
|
||||
} else if (node.largeimageurl) {
|
||||
imageUrl = node.largeimageurl;
|
||||
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();
|
||||
|
||||
// Получаем инфо об узле (для картинки)
|
||||
const { data: unitInfoData, loading: unitInfoLoading, error: unitInfoError } = useQuery(
|
||||
GET_LAXIMO_UNIT_INFO,
|
||||
{
|
||||
variables: { catalogCode, vehicleId, unitId, ssd: ssd || '' },
|
||||
skip: !catalogCode || !vehicleId || !unitId,
|
||||
errorPolicy: 'all',
|
||||
}
|
||||
);
|
||||
// Получаем карту координат
|
||||
const { data: imageMapData, loading: imageMapLoading, error: imageMapError } = useQuery(
|
||||
GET_LAXIMO_UNIT_IMAGE_MAP,
|
||||
{
|
||||
variables: { catalogCode, vehicleId, unitId, ssd: ssd || '' },
|
||||
skip: !catalogCode || !vehicleId || !unitId,
|
||||
errorPolicy: 'all',
|
||||
}
|
||||
);
|
||||
|
||||
const unitInfo = unitInfoData?.laximoUnitInfo;
|
||||
const coordinates = imageMapData?.laximoUnitImageMap?.coordinates || [];
|
||||
const imageUrl = unitInfo?.imageurl ? getImageUrl(unitInfo.imageurl, selectedImageSize) : '';
|
||||
|
||||
// Масштабируем точки после загрузки картинки
|
||||
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) {
|
||||
return <div className="text-center py-8 text-gray-500">Загружаем схему узла...</div>;
|
||||
}
|
||||
if (unitInfoError) {
|
||||
return <div className="text-center py-8 text-red-600">Ошибка загрузки схемы: {unitInfoError.message}</div>;
|
||||
}
|
||||
if (!imageUrl) {
|
||||
return <div className="text-center py-8 text-gray-400">Нет изображения для этого узла</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="knotin">
|
||||
{imageUrl ? (
|
||||
<img src={imageUrl} loading="lazy" alt={node.name || "Изображение узла"} className="image-26" />
|
||||
) : (
|
||||
<div style={{ width: 200, height: 200, background: '#eee', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
Нет изображения
|
||||
<>
|
||||
<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>
|
||||
)}
|
||||
{/* <div style={{ marginTop: 8, fontWeight: 500 }}>{node.name}</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) => {
|
||||
const scaledX = coord.x * imageScale.x;
|
||||
const scaledY = coord.y * imageScale.y;
|
||||
const scaledWidth = coord.width * imageScale.x;
|
||||
const scaledHeight = coord.height * imageScale.y;
|
||||
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 border-2 border-red-600 bg-white rounded-full cursor-pointer"
|
||||
style={{
|
||||
left: scaledX,
|
||||
top: scaledY,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
borderRadius: '50%',
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
title={coord.codeonimage}
|
||||
onClick={() => handlePointClick(coord.codeonimage)}
|
||||
>
|
||||
<span className="flex items-center justify-center w-full h-full text-black text-sm font-bold select-none pointer-events-none">
|
||||
{coord.codeonimage}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Модалка выбора бренда */}
|
||||
<BrandSelectionModal
|
||||
isOpen={isBrandModalOpen}
|
||||
onClose={() => setIsBrandModalOpen(false)}
|
||||
articleNumber={selectedDetail?.oem || ''}
|
||||
detailName={selectedDetail?.name || ''}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import BrandSelectionModal from '../BrandSelectionModal';
|
||||
|
||||
interface KnotPartsProps {
|
||||
parts: Array<{
|
||||
@ -13,39 +14,59 @@ interface KnotPartsProps {
|
||||
note?: string;
|
||||
attributes?: Array<{ key: string; name?: string; value: string }>;
|
||||
}>;
|
||||
selectedCodeOnImage?: string | number;
|
||||
}
|
||||
|
||||
const KnotParts: React.FC<KnotPartsProps> = ({ parts }) => {
|
||||
const router = useRouter();
|
||||
const KnotParts: React.FC<KnotPartsProps> = ({ parts, selectedCodeOnImage }) => {
|
||||
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
|
||||
const [selectedDetail, setSelectedDetail] = useState<{ oem: string; name: string } | null>(null);
|
||||
|
||||
const handlePriceClick = (part: any) => {
|
||||
if (part.oem) {
|
||||
setSelectedDetail({ oem: part.oem, name: part.name || '' });
|
||||
setIsBrandModalOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="knot-parts">
|
||||
{parts.map((part, idx) => (
|
||||
<div className="w-layout-hflex knotlistitem" key={part.detailid || idx}>
|
||||
<div className="w-layout-hflex flex-block-116">
|
||||
<div className="nuberlist">{part.codeonimage || idx + 1}</div>
|
||||
<div className="oemnuber">{part.oem}</div>
|
||||
</div>
|
||||
<div className="partsname">{part.name}</div>
|
||||
<div className="w-layout-hflex flex-block-117">
|
||||
<button
|
||||
className="button-3 w-button"
|
||||
onClick={() => {
|
||||
if (part.oem) {
|
||||
router.push(`/search?q=${encodeURIComponent(part.oem)}&mode=parts`);
|
||||
}
|
||||
}}
|
||||
<>
|
||||
<div className="knot-parts">
|
||||
{parts.map((part, idx) => {
|
||||
const isSelected = part.codeonimage && part.codeonimage === selectedCodeOnImage;
|
||||
return (
|
||||
<div
|
||||
className={`w-layout-hflex knotlistitem border rounded transition-colors duration-150 ${isSelected ? 'bg-yellow-100 border-yellow-400' : 'border-transparent'}`}
|
||||
key={part.detailid || idx}
|
||||
>
|
||||
Цена
|
||||
</button>
|
||||
<div className="code-embed-16 w-embed">
|
||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
|
||||
</svg>
|
||||
<div className="w-layout-hflex flex-block-116">
|
||||
<div className="nuberlist">{part.codeonimage || idx + 1}</div>
|
||||
<div className="oemnuber">{part.oem}</div>
|
||||
</div>
|
||||
<div className="partsname">{part.name}</div>
|
||||
<div className="w-layout-hflex flex-block-117">
|
||||
<button
|
||||
className="button-3 w-button"
|
||||
onClick={() => handlePriceClick(part)}
|
||||
>
|
||||
Цена
|
||||
</button>
|
||||
<div className="code-embed-16 w-embed">
|
||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<BrandSelectionModal
|
||||
isOpen={isBrandModalOpen}
|
||||
onClose={() => setIsBrandModalOpen(false)}
|
||||
articleNumber={selectedDetail?.oem || ''}
|
||||
detailName={selectedDetail?.name || ''}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -303,7 +303,14 @@ const VehicleDetailsPage = () => {
|
||||
<div className="w-layout-hflex flex-block-13">
|
||||
<div className="w-layout-vflex flex-block-14-copy-copy">
|
||||
<button onClick={() => setSelectedNode(null)} style={{ marginBottom: 16 }}>Назад</button>
|
||||
<KnotIn node={selectedNode} />
|
||||
<KnotIn
|
||||
catalogCode={vehicleInfo.catalog}
|
||||
vehicleId={vehicleInfo.vehicleid}
|
||||
ssd={vehicleInfo.ssd}
|
||||
unitId={selectedNode.unitid}
|
||||
unitName={selectedNode.name}
|
||||
parts={unitDetails}
|
||||
/>
|
||||
{unitDetailsLoading ? (
|
||||
<div style={{ padding: 24, textAlign: 'center' }}>Загружаем детали узла...</div>
|
||||
) : unitDetailsError ? (
|
||||
|
@ -386,15 +386,17 @@ input.input-receiver:focus {
|
||||
color: var(--_fonts---color--light-blue-grey);
|
||||
}
|
||||
|
||||
.knotin {
|
||||
/* .knotin {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
.knotin img {
|
||||
max-width: 100%;
|
||||
object-fit: contain; /* или cover */
|
||||
}
|
||||
object-fit: contain;
|
||||
} */
|
||||
|
||||
|
||||
|
||||
.tabs-menu.w-tab-menu {
|
||||
scrollbar-width: none;
|
||||
|
Reference in New Issue
Block a user