Files
protekauto-frontend/src/pages/vehicle-search/[brand]/[vehicleId].tsx
egortriston f6cc95e714 pravki 29.06
2025-06-29 00:39:17 +03:00

431 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useQuery } from '@apollo/client';
import Head from 'next/head';
import Footer from '@/components/Footer';
import Layout from '@/components/Layout';
import VehiclePartsSearchSection from '@/components/VehiclePartsSearchSection';
import LaximoDiagnostic from '@/components/LaximoDiagnostic';
import { GET_LAXIMO_VEHICLE_INFO, GET_LAXIMO_CATALOG_INFO, GET_LAXIMO_UNIT_DETAILS } from '@/lib/graphql';
import { LaximoCatalogInfo } from '@/types/laximo';
import InfoVin from '@/components/vin/InfoVin';
import VinLeftbar from '@/components/vin/VinLeftbar';
import VinKnot from '@/components/vin/VinKnot';
import VinCategory from '@/components/vin/VinCategory';
import PartDetailCard from '@/components/PartDetailCard';
import VinPartCard from '@/components/vin/VinPartCard';
import KnotIn from '@/components/vin/KnotIn';
import KnotParts from '@/components/vin/KnotParts';
interface LaximoVehicleInfo {
vehicleid: string;
name: string;
ssd: string;
brand: string;
catalog: string;
attributes: Array<{
key: string;
name: string;
value: string;
}>;
}
const VehicleDetailsPage = () => {
const router = useRouter();
const { brand, vehicleId, oemNumber, searchType: searchTypeParam } = router.query;
// Устанавливаем тип поиска из URL или по умолчанию
// Важно: согласно документации Laximo, для групп быстрого поиска используется ListQuickGroup
// Если в URL передан searchType=categories, мы интерпретируем это как запрос на quickgroups
let defaultSearchType: 'quickgroups' | 'categories' | 'fulltext' = 'quickgroups';
if (searchTypeParam === 'categories') {
// В URL categories, но мы используем quickgroups для групп быстрого поиска
defaultSearchType = 'quickgroups';
console.log('🔄 URL содержит searchType=categories, интерпретируем как quickgroups (группы быстрого поиска)');
} else if (searchTypeParam === 'quickgroups') {
defaultSearchType = 'quickgroups';
} else if (searchTypeParam === 'fulltext') {
defaultSearchType = 'fulltext';
}
// ====== ВСЕ ХУКИ В НАЧАЛЕ КОМПОНЕНТА ======
const [searchType, setSearchType] = useState<'quickgroups' | 'categories' | 'fulltext'>(defaultSearchType);
const [showKnot, setShowKnot] = useState(false);
const [foundParts, setFoundParts] = useState<any[]>([]);
const [selectedNode, setSelectedNode] = useState<any | null>(null);
const handleCategoryClick = (e?: React.MouseEvent) => {
if (e) e.preventDefault();
setShowKnot(true);
};
useEffect(() => {
const handler = (e: Event) => {
const target = e.target as HTMLElement;
if (target.classList.contains('link-2')) {
e.preventDefault();
setShowKnot(true);
}
};
document.addEventListener('click', handler);
return () => document.removeEventListener('click', handler);
}, []);
// ====== КОНЕЦ ХУКОВ ======
// Получаем информацию о каталоге
const { data: catalogData } = useQuery<{ laximoCatalogInfo: LaximoCatalogInfo }>(
GET_LAXIMO_CATALOG_INFO,
{
variables: { catalogCode: brand },
skip: !brand
}
);
// Получаем информацию о выбранном автомобиле
const ssdFromQuery = Array.isArray(router.query.ssd) ? router.query.ssd[0] : router.query.ssd;
const useStorage = router.query.use_storage === '1';
const ssdLengthFromUrl = router.query.ssd_length ? parseInt(router.query.ssd_length as string) : 0;
// Если указано use_storage, пытаемся получить SSD из localStorage
let finalSsd = '';
if (useStorage && typeof window !== 'undefined') {
const vehicleKey = `vehicle_ssd_${brand}_${vehicleId}`;
const storedSsd = localStorage.getItem(vehicleKey);
if (storedSsd) {
finalSsd = storedSsd;
console.log('🔧 SSD получен из localStorage, длина:', storedSsd.length);
// НЕ ОЧИЩАЕМ SSD сразу, оставляем на случай перезагрузки страницы
// localStorage.removeItem(vehicleKey);
} else {
console.log('⚠️ SSD не найден в localStorage, ключ:', vehicleKey);
console.log('🔍 Все ключи localStorage:', Object.keys(localStorage));
}
} else if (ssdFromQuery && ssdFromQuery.trim() !== '') {
finalSsd = ssdFromQuery;
console.log('🔧 SSD получен из URL');
}
console.log('🔍 Vehicle page params:', {
brand,
vehicleId,
useStorage,
ssdLengthFromUrl,
ssdFromQuery: ssdFromQuery ? `${ssdFromQuery.substring(0, 50)}...` : 'отсутствует',
finalSsd: finalSsd ? `${finalSsd.substring(0, 50)}...` : 'отсутствует',
ssdLength: finalSsd.length
});
const { data: vehicleData, loading: vehicleLoading, error: vehicleError } = useQuery<{ laximoVehicleInfo: LaximoVehicleInfo }>(
GET_LAXIMO_VEHICLE_INFO,
{
variables: {
catalogCode: brand,
vehicleId: vehicleId,
...(finalSsd && { ssd: finalSsd }),
localized: true
},
skip: !brand || !vehicleId,
errorPolicy: 'all'
}
);
// Получаем детали выбранного узла, если он выбран
const {
data: unitDetailsData,
loading: unitDetailsLoading,
error: unitDetailsError
} = useQuery(
GET_LAXIMO_UNIT_DETAILS,
{
variables: selectedNode
? {
catalogCode: selectedNode.catalogCode || selectedNode.catalog || brand,
vehicleId: selectedNode.vehicleId || vehicleId,
unitId: selectedNode.unitid || selectedNode.unitId,
ssd: selectedNode.ssd || finalSsd || '',
}
: { catalogCode: '', vehicleId: '', unitId: '', ssd: '' },
skip: !selectedNode,
errorPolicy: 'all',
}
);
const unitDetails = unitDetailsData?.laximoUnitDetails || [];
// Логируем ошибки
if (vehicleError) {
console.error('Vehicle GraphQL error:', vehicleError);
}
if (vehicleLoading) {
return (
<>
<Head>
<title>Загрузка автомобиля...</title>
</Head>
<main className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-red-600 mx-auto"></div>
<p className="mt-4 text-lg text-gray-600">Загружаем информацию об автомобиле...</p>
</div>
</main>
</>
);
}
// Если информация о каталоге недоступна, показываем ошибку
if (!catalogData?.laximoCatalogInfo) {
return (
<>
<Head>
<title>Каталог не найден</title>
</Head>
<main className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">Каталог не найден</h1>
<p className="text-gray-600 mb-8">Информация о каталоге недоступна</p>
<button
onClick={() => router.back()}
className="bg-red-600 text-white px-6 py-3 rounded-lg hover:bg-red-700 transition-colors"
>
Назад к поиску
</button>
</div>
</main>
</>
);
}
// Если информация об автомобиле недоступна, создаем заглушку
const vehicleInfo = vehicleData?.laximoVehicleInfo || {
vehicleid: vehicleId as string,
name: `Автомобиль ${catalogData.laximoCatalogInfo.name}`,
ssd: finalSsd,
brand: catalogData.laximoCatalogInfo.brand,
catalog: catalogData.laximoCatalogInfo.code,
attributes: []
};
// Если нет данных автомобиля и есть ошибка, показываем предупреждение
const hasError = vehicleError && !vehicleData?.laximoVehicleInfo;
const catalogInfo = catalogData.laximoCatalogInfo;
return (
<>
<Head>
<title>VIN</title>
<meta content="vin" property="og:title" />
<meta content="vin" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
{/* ====== ВРЕМЕННЫЙ МАКЕТ ДЛЯ ВЕРСТКИ (начало) ====== */}
<InfoVin
vehicleName={
vehicleInfo.brand && vehicleInfo.name && vehicleInfo.name.indexOf(vehicleInfo.brand) !== 0
? `${vehicleInfo.brand} ${vehicleInfo.name}`
: vehicleInfo.name
}
vehicleInfo={
vehicleInfo.attributes && vehicleInfo.attributes.length > 0
? vehicleInfo.attributes.map(attr => attr.value).join(' · ')
: ''
}
/>
<div className="w-layout-blockcontainer container-vin w-container">
{!selectedNode ? (
<div className="w-layout-hflex flex-block-13">
<VinLeftbar
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
ssd={vehicleInfo.ssd}
onSearchResults={setFoundParts}
onNodeSelect={setSelectedNode}
/>
{/* Категории или Knot или карточки */}
{foundParts.length > 0 ? (
<div className="knot-parts">
{foundParts.map((detail, idx) => (
<VinPartCard
key={detail.oem + idx}
n={idx + 1}
name={detail.name}
oem={detail.oem}
/>
))}
</div>
) : showKnot ? (
<VinKnot />
) : (
<VinCategory
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
ssd={vehicleInfo.ssd}
onNodeSelect={setSelectedNode}
/>
)}
</div>
) : (
<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} />
{unitDetailsLoading ? (
<div style={{ padding: 24, textAlign: 'center' }}>Загружаем детали узла...</div>
) : unitDetailsError ? (
<div style={{ color: 'red', padding: 24 }}>Ошибка загрузки деталей: {unitDetailsError.message}</div>
) : unitDetails.length > 0 ? (
<KnotParts parts={unitDetails} />
) : (
<div style={{ padding: 24, textAlign: 'center' }}>Детали не найдены</div>
)}
</div>
</div>
)}
</div>
{/* ====== ВРЕМЕННЫЙ МАКЕТ ДЛЯ ВЕРСТКИ (конец) ====== */}
{/* Навигация */}
<nav className="bg-white border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-4">
<button
onClick={() => router.back()}
className="text-gray-500 hover:text-gray-700 flex items-center space-x-2"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
<span>Назад</span>
</button>
<div className="text-sm text-gray-500">
<span>Главная</span>
<span className="mx-2">/</span>
<span>Каталог</span>
<span className="mx-2">/</span>
<span>{catalogInfo.name}</span>
<span className="mx-2">/</span>
<span className="text-gray-900 font-medium">{vehicleInfo.name}</span>
</div>
</div>
</div>
</div>
</nav>
{/* Информация об автомобиле */}
<div className="bg-white border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="flex items-center space-x-4 mb-6">
{catalogInfo.icon && (
<img
src={`/images/brands/${catalogInfo.icon}`}
alt={catalogInfo.name}
className="w-12 h-12 object-contain"
onError={(e) => {
e.currentTarget.style.display = 'none';
}}
/>
)}
<div>
<h1 className="text-2xl font-bold text-gray-900">{vehicleInfo.name}</h1>
<p className="text-lg text-gray-600">{catalogInfo.name}</p>
</div>
</div>
{/* Предупреждение об ошибке */}
{hasError && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-800">
Предупреждение
</h3>
<div className="mt-2 text-sm text-yellow-700">
<p>Не удалось загрузить полную информацию об автомобиле. Отображается базовая информация.</p>
</div>
</div>
</div>
</div>
)}
{/* Отладочная информация */}
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-6">
<h4 className="text-sm font-medium text-gray-900 mb-3">
🔧 Отладочная информация
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-500">Использовать localStorage:</span>
<span className="ml-2 font-medium">{useStorage ? 'Да' : 'Нет'}</span>
</div>
<div>
<span className="text-gray-500">Длина SSD из URL:</span>
<span className="ml-2 font-medium">{ssdLengthFromUrl || 'не указана'}</span>
</div>
<div>
<span className="text-gray-500">SSD получен:</span>
<span className="ml-2 font-medium">{finalSsd ? 'Да' : 'Нет'}</span>
</div>
<div>
<span className="text-gray-500">Длина SSD:</span>
<span className="ml-2 font-medium">{finalSsd.length}</span>
</div>
<div className="md:col-span-2">
<span className="text-gray-500">SSD (первые 100 символов):</span>
<span className="ml-2 font-mono text-xs break-all">
{finalSsd ? finalSsd.substring(0, 100) + '...' : 'отсутствует'}
</span>
</div>
</div>
</div>
{/* Характеристики автомобиля */}
{vehicleInfo.attributes && vehicleInfo.attributes.length > 0 && (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{vehicleInfo.attributes.map((attr, index) => (
<div key={index} className="bg-gray-50 rounded-lg p-3">
<dt className="text-sm font-medium text-gray-500">{attr.name}</dt>
<dd className="text-sm text-gray-900 mt-1">{attr.value}</dd>
</div>
))}
</div>
)}
</div>
</div>
{/* Способы поиска запчастей */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="mb-6">
<h2 className="text-xl font-bold text-gray-900 mb-2">Поиск запчастей</h2>
<p className="text-gray-600">
Выберите способ поиска запчастей для вашего автомобиля
</p>
</div>
{/* Диагностический компонент */}
<LaximoDiagnostic
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
ssd={vehicleInfo.ssd}
/>
<VehiclePartsSearchSection
catalogInfo={catalogInfo}
vehicleInfo={vehicleInfo}
searchType={searchType}
onSearchTypeChange={setSearchType}
/>
</div>
</>
);
};
export default VehicleDetailsPage;