Обновлены компоненты VehicleSearchResults и InfoVin для поддержки отображения дополнительных атрибутов автомобиля. В VehicleSearchResults добавлена логика для отображения атрибутов из API, а в InfoVin реализован вывод основных параметров и tooltip с полной информацией об автомобиле. Обновлены GraphQL запросы для получения атрибутов. Исправлены типы для LaximoVehicleSearchResult.

This commit is contained in:
Bivekich
2025-07-02 17:52:55 +03:00
parent 35d807f234
commit a9e4b74179
5 changed files with 318 additions and 74 deletions

View File

@ -90,53 +90,68 @@ const VehicleSearchResults: React.FC<VehicleSearchResultsProps> = ({
<h5 className="text-sm font-semibold text-gray-700 mb-2">Основные характеристики</h5>
{renderAttribute('Марка', vehicle.brand)}
{renderAttribute('Модель', vehicle.model)}
{renderAttribute('Год', vehicle.year)}
{renderAttribute('Кузов', vehicle.bodytype)}
{renderAttribute('Двигатель', vehicle.engine)}
{renderAttribute('Трансмиссия', vehicle.transmission)}
</div>
{/* Дополнительные характеристики */}
<div className="space-y-1 mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Дополнительные характеристики</h5>
{renderAttribute('Класс', vehicle.grade)}
{renderAttribute('Цвет кузова', vehicle.framecolor)}
{renderAttribute('Цвет салона', vehicle.trimcolor)}
{renderAttribute('Рынок', vehicle.market)}
{renderAttribute('Регион производства', vehicle.creationregion)}
{renderAttribute('Регион назначения', vehicle.destinationregion)}
</div>
{/* Технические характеристики */}
<div className="space-y-1 mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Технические характеристики</h5>
{renderAttribute('Информация о двигателе', vehicle.engine_info)}
{renderAttribute('Номер двигателя', vehicle.engineno)}
{renderAttribute('Дата производства', vehicle.date)}
{renderAttribute('Произведен', vehicle.manufactured)}
{renderAttribute('Период производства', vehicle.prodPeriod)}
{renderAttribute('Диапазон производства', vehicle.prodRange)}
</div>
{/* Даты и периоды */}
<div className="space-y-1 mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Даты и периоды</h5>
{renderAttribute('Дата с', vehicle.datefrom)}
{renderAttribute('Дата по', vehicle.dateto)}
{renderAttribute('Модельный год с', vehicle.modelyearfrom)}
{renderAttribute('Модельный год по', vehicle.modelyearto)}
</div>
{/* Опции и описание */}
{(vehicle.options || vehicle.description || vehicle.notes) && (
{/* Все атрибуты из API */}
{vehicle.attributes && vehicle.attributes.length > 0 && (
<div className="space-y-1 mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Опции и описание</h5>
{renderAttribute('Опции', vehicle.options)}
{renderAttribute('Описание', vehicle.description)}
{renderAttribute('Примечания', vehicle.notes)}
<h5 className="text-sm font-semibold text-gray-700 mb-2">Дополнительные характеристики</h5>
{vehicle.attributes.map((attr, attrIndex) => (
<div key={attrIndex} className="flex justify-between py-1 border-b border-gray-100">
<span className="text-sm text-gray-600 font-medium">{attr.name || attr.key}:</span>
<span className="text-sm text-gray-900">{attr.value}</span>
</div>
))}
</div>
)}
{/* Технические характеристики (fallback для старых данных) */}
{(!vehicle.attributes || vehicle.attributes.length === 0) && (
<>
<div className="space-y-1 mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Дополнительные характеристики</h5>
{renderAttribute('Год', vehicle.year)}
{renderAttribute('Кузов', vehicle.bodytype)}
{renderAttribute('Трансмиссия', vehicle.transmission)}
{renderAttribute('Класс', vehicle.grade)}
{renderAttribute('Цвет кузова', vehicle.framecolor)}
{renderAttribute('Цвет салона', vehicle.trimcolor)}
{renderAttribute('Рынок', vehicle.market)}
{renderAttribute('Регион производства', vehicle.creationregion)}
{renderAttribute('Регион назначения', vehicle.destinationregion)}
</div>
<div className="space-y-1 mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Технические характеристики</h5>
{renderAttribute('Информация о двигателе', vehicle.engine_info)}
{renderAttribute('Номер двигателя', vehicle.engineno)}
{renderAttribute('Дата производства', vehicle.date)}
{renderAttribute('Произведен', vehicle.manufactured)}
{renderAttribute('Период производства', vehicle.prodPeriod)}
{renderAttribute('Диапазон производства', vehicle.prodRange)}
</div>
<div className="space-y-1 mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Даты и периоды</h5>
{renderAttribute('Дата с', vehicle.datefrom)}
{renderAttribute('Дата по', vehicle.dateto)}
{renderAttribute('Модельный год с', vehicle.modelyearfrom)}
{renderAttribute('Модельный год по', vehicle.modelyearto)}
</div>
{/* Опции и описание */}
{(vehicle.options || vehicle.description || vehicle.notes) && (
<div className="space-y-1 mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Опции и описание</h5>
{renderAttribute('Опции', vehicle.options)}
{renderAttribute('Описание', vehicle.description)}
{renderAttribute('Примечания', vehicle.notes)}
</div>
)}
</>
)}
{/* Системная информация */}
<div className="mt-4 pt-3 border-t border-gray-200">
<div className="text-xs text-gray-400 space-y-1">

View File

@ -1,47 +1,249 @@
import React from "react";
import React, { useState, useEffect, useRef } from "react";
interface VehicleAttribute {
key: string;
name: string;
value: string;
}
interface InfoVinProps {
vehicleName?: string;
vehicleInfo?: string;
vehicleAttributes?: VehicleAttribute[];
}
const InfoVin: React.FC<InfoVinProps> = ({
vehicleName = "VIN декодирование",
vehicleInfo = "Поиск запчастей по VIN номеру автомобиля"
}) => (
<section className="section-info">
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
<div className="w-layout-hflex flex-block-7">
<a href="#" className="link-block w-inline-block">
<div>Главная</div>
</a>
<div className="text-block-3"></div>
<a href="/brands" className="link-block w-inline-block">
<div>Оригинальный каталог</div>
</a>
<div className="text-block-3"></div>
<a href="#" className="link-block-2 w-inline-block">
<div>{vehicleName}</div>
</a>
</div>
<div className="w-layout-hflex flex-block-8">
<div className="w-layout-hflex flex-block-10">
<h1 className="heading">{vehicleName}</h1>
vehicleInfo = "Поиск запчастей по VIN номеру автомобиля",
vehicleAttributes = []
}) => {
const [showTooltip, setShowTooltip] = useState(false);
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const iconRef = useRef<HTMLDivElement>(null);
// Отладочный вывод атрибутов
useEffect(() => {
if (vehicleAttributes.length > 0) {
console.log('🚗 Атрибуты автомобиля:', vehicleAttributes);
console.log('🔍 Ключи атрибутов:', vehicleAttributes.map(attr => ({ key: attr.key, name: attr.name })));
}
}, [vehicleAttributes]);
// Определяем основные параметры для отображения
const getMainParameters = (attributes: VehicleAttribute[]) => {
// Приоритетные ключи для основных параметров
const priorityKeys = [
// Двигатель
{
keys: ['engine', 'enginetype', 'engine_type', 'двигатель', 'тип двигателя', 'motor'],
priority: 1
},
// VIN
{
keys: ['vin', 'вин', 'vin_code'],
priority: 2
},
// Год выпуска
{
keys: ['year', 'год', 'год выпуска', 'production_year', 'model_year'],
priority: 3
},
// Топливо
{
keys: ['fuel', 'топливо', 'тип топлива', 'fuel_type', 'fueltype'],
priority: 4
},
// Коробка передач
{
keys: ['transmission', 'коробка', 'кпп', 'gearbox', 'transmissiontype'],
priority: 5
}
];
const foundParams: Array<{ attr: VehicleAttribute; priority: number }> = [];
// Ищем атрибуты по приоритетным ключам
for (const priorityGroup of priorityKeys) {
const foundAttr = attributes.find(attr =>
priorityGroup.keys.some(key =>
attr.key.toLowerCase().includes(key.toLowerCase()) ||
attr.name.toLowerCase().includes(key.toLowerCase())
)
);
if (foundAttr) {
foundParams.push({ attr: foundAttr, priority: priorityGroup.priority });
}
}
// Сортируем по приоритету и берем максимум 4 параметра
foundParams.sort((a, b) => a.priority - b.priority);
const mainParams = foundParams.slice(0, 4).map(item => item.attr);
// Если основных параметров меньше 3, добавляем первые доступные
if (mainParams.length < 3) {
const additionalParams = attributes
.filter(attr => !mainParams.includes(attr))
.slice(0, 3 - mainParams.length);
return [...mainParams, ...additionalParams];
}
return mainParams;
};
const mainParameters = getMainParameters(vehicleAttributes);
const displayText = mainParameters.length > 0
? mainParameters.map(attr => attr.value).join(' · ')
: vehicleInfo;
// Отладочный вывод выбранных параметров
useEffect(() => {
if (mainParameters.length > 0) {
console.log('✅ Выбранные основные параметры:', mainParameters);
console.log('📝 Отображаемый текст:', displayText);
}
}, [mainParameters, displayText]);
// Вычисляем позицию tooltip
const calculateTooltipPosition = () => {
if (iconRef.current) {
const rect = iconRef.current.getBoundingClientRect();
const tooltipWidth = 500;
const tooltipHeight = 300; // примерная высота
let x = rect.left + rect.width / 2 - tooltipWidth / 2;
let y = rect.bottom + 8;
// Проверяем, не выходит ли tooltip за границы экрана
if (x < 10) x = 10;
if (x + tooltipWidth > window.innerWidth - 10) {
x = window.innerWidth - tooltipWidth - 10;
}
// Если tooltip не помещается снизу, показываем сверху
if (y + tooltipHeight > window.innerHeight - 10) {
y = rect.top - tooltipHeight - 8;
}
setTooltipPosition({ x, y });
}
};
const handleMouseEnter = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
calculateTooltipPosition();
setShowTooltip(true);
}, 300); // Задержка 300ms
};
const handleMouseLeave = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
setShowTooltip(false);
}, 100); // Небольшая задержка перед скрытием
};
// Очищаем таймеры при размонтировании
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return (
<>
<section className="section-info">
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
<div className="w-layout-hflex flex-block-7">
<a href="#" className="link-block w-inline-block">
<div>Главная</div>
</a>
<div className="text-block-3"></div>
<a href="/brands" className="link-block w-inline-block">
<div>Оригинальный каталог</div>
</a>
<div className="text-block-3"></div>
<a href="#" className="link-block-2 w-inline-block">
<div>{vehicleName}</div>
</a>
</div>
<div className="w-layout-hflex flex-block-8">
<div className="w-layout-hflex flex-block-10">
<h1 className="heading">{vehicleName}</h1>
</div>
</div>
</div>
<div className="w-layout-hflex flex-block-112">
<div className="text-block-55">{displayText}</div>
<div className="relative inline-block">
<div
ref={iconRef}
className="w-embed cursor-pointer hover:opacity-70 transition-opacity"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
role="button"
tabIndex={0}
aria-label="Показать полную информацию об автомобиле"
onFocus={handleMouseEnter}
onBlur={handleMouseLeave}
>
<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 className="w-layout-hflex flex-block-112">
<div className="text-block-55">{vehicleInfo}</div>
<div className="w-embed">
{/* SVG icon */}
<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>
</section>
{/* Tooltip с фиксированным позиционированием */}
{showTooltip && vehicleAttributes.length > 0 && (
<div
className="fixed w-[500px] max-w-[90vw] bg-white border border-gray-200 rounded-lg shadow-xl z-[9999] p-4 animate-in fade-in-0 zoom-in-95 duration-200"
style={{
left: `${tooltipPosition.x}px`,
top: `${tooltipPosition.y}px`,
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{/* Заголовок */}
<div className="mb-3 pb-2 border-b border-gray-100">
<h3 className="text-sm font-semibold text-gray-900">
Полная информация об автомобиле
</h3>
<p className="text-xs text-gray-600 mt-1">{vehicleName}</p>
</div>
{/* Атрибуты в сетке */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{vehicleAttributes.map((attr, index) => (
<div key={index} className="flex flex-col">
<dt className="text-xs font-medium text-gray-500 mb-1">{attr.name}</dt>
<dd className="text-xs text-gray-900 break-words">{attr.value}</dd>
</div>
))}
</div>
{/* Подвал */}
<div className="mt-3 pt-2 border-t border-gray-100">
<div className="text-xs text-gray-500 text-center">
Всего параметров: {vehicleAttributes.length}
</div>
</div>
</div>
</div>
</div>
</section>
);
)}
</>
);
};
export default InfoVin;

View File

@ -569,6 +569,11 @@ export const FIND_LAXIMO_VEHICLE_BY_WIZARD = gql`
options
description
grade
attributes {
key
name
value
}
}
}
`;
@ -606,6 +611,11 @@ export const FIND_LAXIMO_VEHICLE_BY_PLATE = gql`
options
description
grade
attributes {
key
name
value
}
}
}
`;
@ -643,6 +653,11 @@ export const FIND_LAXIMO_VEHICLE_BY_PLATE_GLOBAL = gql`
options
description
grade
attributes {
key
name
value
}
}
}
`;
@ -686,6 +701,11 @@ export const FIND_LAXIMO_APPLICABLE_VEHICLES = gql`
options
description
grade
attributes {
key
name
value
}
}
}
`;
@ -731,6 +751,11 @@ export const FIND_LAXIMO_VEHICLES_BY_PART_NUMBER = gql`
options
description
grade
attributes {
key
name
value
}
}
}
}

View File

@ -312,6 +312,7 @@ const VehicleDetailsPage = () => {
? vehicleInfo.attributes.map(attr => attr.value).join(' · ')
: ''
}
vehicleAttributes={vehicleInfo.attributes || []}
/>
<div className="w-layout-blockcontainer container-vin w-container">

View File

@ -108,6 +108,7 @@ export interface LaximoVehicleSearchResult {
// Дополнительные атрибуты (могут приходить в виде массива attributes)
sales_code?: string
attributes: LaximoVehicleAttribute[]
}
export interface LaximoVehicleInfo {