pravki 29.06 #3

Merged
egortriston merged 1 commits from pravki2906 into main 2025-06-29 00:42:23 +03:00
10 changed files with 693 additions and 237 deletions
Showing only changes of commit f6cc95e714 - Show all commits

View File

@ -1,6 +1,11 @@
import React from "react"; import React from "react";
const InfoVin = () => ( interface InfoVinProps {
vehicleName: string;
vehicleInfo: string;
}
const InfoVin: React.FC<InfoVinProps> = ({ vehicleName, vehicleInfo }) => (
<section className="section-info"> <section className="section-info">
<div className="w-layout-blockcontainer container info w-container"> <div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9"> <div className="w-layout-vflex flex-block-9">
@ -9,22 +14,22 @@ const InfoVin = () => (
<div>Главная</div> <div>Главная</div>
</a> </a>
<div className="text-block-3"></div> <div className="text-block-3"></div>
<a href="#" className="link-block w-inline-block"> <a href="/brands" className="link-block w-inline-block">
<div>Оригинальный каталог</div> <div>Оригинальный каталог</div>
</a> </a>
<div className="text-block-3"></div> <div className="text-block-3"></div>
<a href="#" className="link-block-2 w-inline-block"> <a href="#" className="link-block-2 w-inline-block">
<div>Audi Q7</div> <div>{vehicleName}</div>
</a> </a>
</div> </div>
<div className="w-layout-hflex flex-block-8"> <div className="w-layout-hflex flex-block-8">
<div className="w-layout-hflex flex-block-10"> <div className="w-layout-hflex flex-block-10">
<h1 className="heading">Audi Q7</h1> <h1 className="heading">{vehicleName}</h1>
</div> </div>
</div> </div>
</div> </div>
<div className="w-layout-hflex flex-block-112"> <div className="w-layout-hflex flex-block-112">
<div className="text-block-55">WAUZZZ4M6JD010702 · 2018 · SUQ(8A) · CVMD · 3000CC / 249hp / 183kW TDI CR</div> <div className="text-block-55">{vehicleInfo}</div>
<div className="w-embed"> <div className="w-embed">
{/* SVG icon */} {/* SVG icon */}
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@ -1,9 +1,36 @@
import React from "react"; import React from "react";
const KnotIn = () => ( // Функция для корректного формирования URL изображения
<div className="knotin"> const getImageUrl = (baseUrl: string, size: string) => {
<img src="/images/image-44.jpg" loading="lazy" alt="" className="image-26" /> if (!baseUrl) return '';
</div> return baseUrl
); .replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.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;
}
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>
)}
{/* <div style={{ marginTop: 8, fontWeight: 500 }}>{node.name}</div> */}
</div>
);
};
export default KnotIn; export default KnotIn;

View File

@ -1,43 +1,52 @@
import React from "react"; import React from "react";
import { useRouter } from "next/router";
const parts = [ interface KnotPartsProps {
{ n: 1, oem: "059198405B", name: "Фильтрующий элемент с проклад." }, parts: Array<{
{ n: 2, oem: "N 10196103", name: "Винт с цилиндр. скруглённой головкой Torx" }, detailid?: string;
{ n: 3, oem: "059117070J", name: "Уплотнитель" }, codeonimage?: string | number;
{ n: 4, oem: "N 10124306", name: "Винт с плоской головкой и внутренним Torx" }, oem?: string;
{ n: 5, oem: "059117015K", name: "Масляный радиатор" }, name?: string;
{ n: 6, oem: "059117015K", name: "Масляный радиатор" }, price?: string | number;
{ n: 7, oem: "059117015K", name: "Масляный радиатор" }, brand?: string;
{ n: 8, oem: "059117015K", name: "Масляный радиатор" }, availability?: string;
{ n: 9, oem: "059117015K", name: "Масляный радиатор" }, note?: string;
{ n: 10, oem: "059117015K", name: "Масляный радиатор" }, attributes?: Array<{ key: string; name?: string; value: string }>;
{ n: 11, oem: "059117015K", name: "Масляный радиатор" }, }>;
{ n: 12, oem: "059117015K", name: "Масляный радиатор" }, }
{ n: 13, oem: "059117015K", name: "Масляный радиатор" },
{ n: 14, oem: "059117015K", name: "Масляный радиатор" },
{ n: 15, oem: "059117015K", name: "Масляный радиатор" },
];
const KnotParts = () => ( const KnotParts: React.FC<KnotPartsProps> = ({ parts }) => {
<div className="knot-parts"> const router = useRouter();
{parts.map((part, idx) => ( return (
<div className="w-layout-hflex knotlistitem" key={idx}> <div className="knot-parts">
<div className="w-layout-hflex flex-block-116"> {parts.map((part, idx) => (
<div className="nuberlist">{part.n}</div> <div className="w-layout-hflex knotlistitem" key={part.detailid || idx}>
<div className="oemnuber">{part.oem}</div> <div className="w-layout-hflex flex-block-116">
</div> <div className="nuberlist">{part.codeonimage || idx + 1}</div>
<div className="partsname">{part.name}</div> <div className="oemnuber">{part.oem}</div>
<div className="w-layout-hflex flex-block-117"> </div>
<a href="#" className="button-3 w-button">Цена</a> <div className="partsname">{part.name}</div>
<div className="code-embed-16 w-embed"> <div className="w-layout-hflex flex-block-117">
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <button
<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" /> className="button-3 w-button"
</svg> onClick={() => {
if (part.oem) {
router.push(`/search?q=${encodeURIComponent(part.oem)}&mode=parts`);
}
}}
>
Цена
</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>
</div> );
); };
export default KnotParts; export default KnotParts;

View File

@ -1,40 +1,128 @@
import React from "react"; import React, { useState, useRef } from "react";
import { useQuery, useLazyQuery } from "@apollo/client";
const categories = [ import { GET_LAXIMO_CATEGORIES, GET_LAXIMO_UNITS } from "@/lib/graphql/laximo";
"Детали для ТО",
"Двигатель",
"Топливная система",
"Система охлаждения",
"Система выпуска",
"Трансмиссия",
"Ходовая часть",
"Рулевое управление",
"Тормозная система",
"Электрооборудование",
"Отопление / кондиционирование",
"Детали салона",
"Детали кузова",
"Дополнительное оборудование"
];
interface VinCategoryProps { interface VinCategoryProps {
onCategoryClick?: (e: React.MouseEvent) => void; catalogCode: string;
vehicleId: string;
ssd?: string;
onNodeSelect?: (node: any) => void;
} }
const VinCategory: React.FC<VinCategoryProps> = ({ onCategoryClick }) => ( const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd, onNodeSelect }) => {
<div className="w-layout-vflex flex-block-14-copy-copy"> const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
{categories.map((cat, idx) => ( variables: { catalogCode, vehicleId, ssd },
<div className="div-block-131" key={idx} onClick={onCategoryClick} style={{ cursor: onCategoryClick ? 'pointer' : undefined }}> skip: !catalogCode || !vehicleId,
<div className="text-block-57">{cat}</div> errorPolicy: "all",
<div className="w-embed"> });
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> const categories = categoriesData?.laximoCategories || [];
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path> const [unitsByCategory, setUnitsByCategory] = useState<{ [key: string]: any[] }>({});
</svg> const [getUnits] = useLazyQuery(GET_LAXIMO_UNITS, {
</div> onCompleted: (data) => {
</div> if (data && data.laximoUnits && lastCategoryIdRef.current) {
))} setUnitsByCategory((prev) => ({
</div> ...prev,
); [lastCategoryIdRef.current!]: data.laximoUnits || [],
}));
}
},
});
const [selectedCategory, setSelectedCategory] = useState<any | null>(null);
const lastCategoryIdRef = useRef<string | null>(null);
// Если выбрана категория — показываем подкатегории (children или units)
let subcategories: any[] = [];
if (selectedCategory) {
if (selectedCategory.children && selectedCategory.children.length > 0) {
subcategories = selectedCategory.children;
} else {
subcategories = unitsByCategory[selectedCategory.quickgroupid] || [];
}
}
const handleCategoryClick = (cat: any) => {
if (cat.children && cat.children.length > 0) {
setSelectedCategory(cat);
} else {
// Если нет children, грузим units (подкатегории)
if (!unitsByCategory[cat.quickgroupid]) {
lastCategoryIdRef.current = cat.quickgroupid;
getUnits({ variables: { catalogCode, vehicleId, ssd, categoryId: cat.quickgroupid } });
}
setSelectedCategory(cat);
}
};
const handleBack = () => {
setSelectedCategory(null);
};
const handleSubcategoryClick = (subcat: any) => {
if (onNodeSelect) {
onNodeSelect({
...subcat,
unitid: subcat.unitid || subcat.quickgroupid || subcat.id,
});
}
};
if (categoriesLoading) return <div>Загрузка категорий...</div>;
if (categoriesError) return <div style={{ color: "red" }}>Ошибка: {categoriesError.message}</div>;
return (
<div className="w-layout-vflex flex-block-14-copy-copy">
{!selectedCategory ? (
// Список категорий
categories.map((cat: any, idx: number) => (
<div
className="div-block-131"
key={cat.quickgroupid || cat.id || idx}
onClick={() => handleCategoryClick(cat)}
style={{ cursor: "pointer" }}
>
<div className="text-block-57">{cat.name}</div>
<div className="w-embed">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path>
</svg>
</div>
</div>
))
) : (
// Список подкатегорий (children или units)
<>
<div className="div-block-131" onClick={handleBack} style={{ cursor: "pointer", fontWeight: 500 }}>
<div className="text-block-57"> Назад</div>
<div className="w-embed">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path>
</svg>
</div>
</div>
{subcategories.length === 0 && <div style={{ color: "#888", padding: 8 }}>Нет подкатегорий</div>}
{subcategories.map((subcat: any, idx: number) => (
<div
className="div-block-131"
key={subcat.quickgroupid || subcat.unitid || subcat.id || idx}
onClick={() => handleSubcategoryClick(subcat)}
style={{ cursor: "pointer" }}
>
<div className="text-block-57">{subcat.name}</div>
<div className="w-embed">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path>
</svg>
</div>
</div>
))}
</>
)}
</div>
);
};
export default VinCategory; export default VinCategory;

View File

@ -1,35 +1,86 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { useLazyQuery, useQuery } from '@apollo/client';
import { SEARCH_LAXIMO_FULLTEXT, GET_LAXIMO_CATEGORIES, GET_LAXIMO_UNITS } from '@/lib/graphql/laximo';
const dropdownTitles = [ interface VinLeftbarProps {
"Детали для ТО", catalogCode?: string;
"Двигатель", vehicleId?: string;
"Топливная система", ssd?: string;
"Система охлаждения", onSearchResults?: (results: any[]) => void;
"Система выпуска", onNodeSelect?: (node: any) => void;
"Трансмиссия", }
"Ходовая часть",
"Рулевое управление",
"Тормозная система",
"Электрооборудование",
"Отопление / кондиционирование",
"Детали салона",
"Детали кузова",
"Дополнительное оборудование"
];
const VinLeftbar = () => { const VinLeftbar: React.FC<VinLeftbarProps> = ({ catalogCode, vehicleId, ssd, onSearchResults, onNodeSelect }) => {
const [openIndex, setOpenIndex] = useState<number | null>(null); const [openIndex, setOpenIndex] = useState<number | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [activeTab, setActiveTab] = useState<'uzly' | 'manufacturer'>('uzly');
const [executeSearch, { data, loading, error }] = useLazyQuery(SEARCH_LAXIMO_FULLTEXT, { errorPolicy: 'all' });
const handleToggle = (idx: number) => { const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
variables: { catalogCode, vehicleId, ssd },
skip: !catalogCode || !vehicleId,
errorPolicy: 'all'
});
const categories = categoriesData?.laximoCategories || [];
const [unitsByCategory, setUnitsByCategory] = useState<{ [key: string]: any[] }>({});
const [getUnits] = useLazyQuery(GET_LAXIMO_UNITS, {
onCompleted: (data) => {
if (data && data.laximoUnits && lastCategoryIdRef.current) {
setUnitsByCategory(prev => ({
...prev,
[lastCategoryIdRef.current!]: data.laximoUnits || []
}));
}
}
});
const lastCategoryIdRef = React.useRef<string | null>(null);
const handleToggle = (idx: number, categoryId: string) => {
setOpenIndex(openIndex === idx ? null : idx); setOpenIndex(openIndex === idx ? null : idx);
if (openIndex !== idx && !unitsByCategory[categoryId]) {
lastCategoryIdRef.current = categoryId;
getUnits({ variables: { catalogCode, vehicleId, ssd, categoryId } });
}
}; };
const handleSearch = () => {
if (!searchQuery.trim() || !catalogCode || !vehicleId || !ssd) return;
executeSearch({
variables: {
catalogCode,
vehicleId,
searchQuery: searchQuery.trim(),
ssd
}
});
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
handleSearch();
}
};
const searchResults = data?.laximoFulltextSearch;
useEffect(() => {
if (searchResults && onSearchResults) {
onSearchResults(searchResults.details || []);
}
if (!searchQuery.trim() && onSearchResults) {
onSearchResults([]);
}
}, [searchResults, searchQuery, onSearchResults]);
return ( return (
<div className="w-layout-vflex vinleftbar"> <div className="w-layout-vflex vinleftbar">
<div className="div-block-2"> <div className="div-block-2">
<div className="form-block w-form"> <div className="form-block w-form">
<form id="wf-form-search" name="wf-form-search" data-name="search" action="http://search" method="post" className="form" data-wf-page-id="685d5478c4ebd5c8793f8c54" data-wf-element-id="14d3a852-00ba-b161-8849a97059b3785d"> <form id="vin-form-search" name="vin-form-search" data-name="vin-form-search" action="#" method="post" className="form">
<a href="#" className="link-block-3 w-inline-block"> <a href="#" className="link-block-3 w-inline-block" onClick={e => { e.preventDefault(); handleSearch(); }}>
<div className="code-embed-6 w-embed"> <div className="code-embed-6 w-embed">
{/* SVG */} {/* SVG */}
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -38,48 +89,124 @@ const VinLeftbar = () => {
</svg> </svg>
</div> </div>
</a> </a>
<input className="text-field w-input" maxLength={256} name="Search" data-name="Search" placeholder="Поиск по названию детали" type="text" id="Search-4" required /> <input
className="text-field w-input"
maxLength={256}
name="VinSearch"
data-name="VinSearch"
placeholder="Поиск по названию детали"
type="text"
id="VinSearchInput"
required
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
onKeyDown={handleKeyDown}
disabled={!catalogCode || !vehicleId || !ssd}
/>
</form> </form>
<div className="success-message w-form-done"> {error && <div style={{ color: 'red', fontSize: 12 }}>Ошибка поиска: {error.message}</div>}
<div>Thank you! Your submission has been received!</div>
</div>
<div className="error-message w-form-fail">
<div>Oops! Something went wrong while submitting the form.</div>
</div>
</div> </div>
</div> </div>
<div className="w-layout-vflex flex-block-113"> <div className="w-layout-vflex flex-block-113">
<div className="w-layout-hflex flex-block-114"> <div className="w-layout-hflex flex-block-114">
<a href="#" className="button-3 w-button">Узлы</a> <a
<a href="#" className="button-23 w-button">От производителя</a> href="#"
className={
searchQuery
? 'button-23 w-button'
: activeTab === 'uzly'
? 'button-3 w-button'
: 'button-23 w-button'
}
onClick={e => {
e.preventDefault();
if (searchQuery) setSearchQuery('');
setActiveTab('uzly');
}}
>
Узлы
</a>
<a
href="#"
className={
searchQuery
? 'button-23 w-button'
: activeTab === 'manufacturer'
? 'button-3 w-button'
: 'button-23 w-button'
}
onClick={e => {
e.preventDefault();
if (searchQuery) setSearchQuery('');
setActiveTab('manufacturer');
}}
>
От производителя
</a>
</div> </div>
{/* Dropdowns start */} {/* Tab content start */}
{dropdownTitles.map((title, idx) => { {activeTab === 'uzly' ? (
const isOpen = openIndex === idx; categoriesLoading ? (
return ( <div style={{ padding: 16, textAlign: 'center' }}>Загружаем категории...</div>
<div ) : categoriesError ? (
key={idx} <div style={{ color: 'red', padding: 16 }}>Ошибка загрузки категорий: {categoriesError.message}</div>
data-hover="false" ) : (
data-delay="0" <>
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`} {categories.map((category: any, idx: number) => {
> const isOpen = openIndex === idx;
<div // Подкатегории: сначала children, если нет — unitsByCategory
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`} const subcategories = category.children && category.children.length > 0
onClick={() => handleToggle(idx)} ? category.children
style={{ cursor: "pointer" }} : unitsByCategory[category.quickgroupid] || [];
> return (
<div className="w-icon-dropdown-toggle"></div> <div
<div className="text-block-56">{title}</div> key={category.quickgroupid}
</div> data-hover="false"
<nav className={`dropdown-list-4 w-dropdown-list${isOpen ? " w--open" : ""}`}> data-delay="0"
<a href="#" className="dropdown-link-3 w-dropdown-link">Link 1</a> className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
<a href="#" className="dropdown-link-3 w-dropdown-link">Link 2</a> >
<a href="#" className="dropdown-link-3 w-dropdown-link">Link 3</a> <div
</nav> className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
</div> onClick={() => handleToggle(idx, category.quickgroupid)}
); style={{ cursor: "pointer" }}
})} >
{/* Dropdowns end */} <div className="w-icon-dropdown-toggle"></div>
<div className="text-block-56">{category.name}</div>
</div>
<nav className={`dropdown-list-4 w-dropdown-list${isOpen ? " w--open" : ""}`}>
{subcategories.length > 0 ? (
subcategories.map((subcat: any) => (
<a
href="#"
key={subcat.quickgroupid || subcat.unitid}
className="dropdown-link-3 w-dropdown-link"
onClick={e => {
e.preventDefault();
if (onNodeSelect) {
onNodeSelect({
...subcat,
unitid: subcat.unitid || subcat.quickgroupid || subcat.id
});
}
}}
>
{subcat.name}
</a>
))
) : (
<span style={{ color: '#888', padding: 8 }}>Нет подкатегорий</span>
)}
</nav>
</div>
);
})}
</>
)
) : (
// Manufacturer tab content (заглушка)
<div style={{ padding: '16px', color: '#888' }}>Здесь будет контент "От производителя"</div>
)}
{/* Tab content end */}
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,38 @@
import React from "react";
import { useRouter } from 'next/router';
interface VinPartCardProps {
n?: number;
oem: string;
name: string;
onPriceClick?: () => void;
}
const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick }) => {
const router = useRouter();
const handlePriceClick = (e: React.MouseEvent) => {
e.preventDefault();
if (onPriceClick) onPriceClick();
if (oem) router.push(`/search?q=${encodeURIComponent(oem)}&mode=parts`);
};
return (
<div className="w-layout-hflex knotlistitem">
<div className="w-layout-hflex flex-block-116">
{n !== undefined && <div className="nuberlist">{n}</div>}
<div className="oemnuber">{oem}</div>
</div>
<div className="partsname">{name}</div>
<div className="w-layout-hflex flex-block-117">
<a href="#" className="button-3 w-button" onClick={handlePriceClick}>Цена</a>
<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>
);
};
export default VinPartCard;

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import Head from 'next/head'; import Head from 'next/head';
@ -6,8 +6,16 @@ import Footer from '@/components/Footer';
import Layout from '@/components/Layout'; import Layout from '@/components/Layout';
import VehiclePartsSearchSection from '@/components/VehiclePartsSearchSection'; import VehiclePartsSearchSection from '@/components/VehiclePartsSearchSection';
import LaximoDiagnostic from '@/components/LaximoDiagnostic'; import LaximoDiagnostic from '@/components/LaximoDiagnostic';
import { GET_LAXIMO_VEHICLE_INFO, GET_LAXIMO_CATALOG_INFO } from '@/lib/graphql'; import { GET_LAXIMO_VEHICLE_INFO, GET_LAXIMO_CATALOG_INFO, GET_LAXIMO_UNIT_DETAILS } from '@/lib/graphql';
import { LaximoCatalogInfo } from '@/types/laximo'; 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 { interface LaximoVehicleInfo {
vehicleid: string; vehicleid: string;
@ -41,7 +49,27 @@ const VehicleDetailsPage = () => {
defaultSearchType = 'fulltext'; defaultSearchType = 'fulltext';
} }
// ====== ВСЕ ХУКИ В НАЧАЛЕ КОМПОНЕНТА ======
const [searchType, setSearchType] = useState<'quickgroups' | 'categories' | 'fulltext'>(defaultSearchType); 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 }>( const { data: catalogData } = useQuery<{ laximoCatalogInfo: LaximoCatalogInfo }>(
@ -100,6 +128,28 @@ const VehicleDetailsPage = () => {
} }
); );
// Получаем детали выбранного узла, если он выбран
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) { if (vehicleError) {
console.error('Vehicle GraphQL error:', vehicleError); console.error('Vehicle GraphQL error:', vehicleError);
@ -107,7 +157,7 @@ const VehicleDetailsPage = () => {
if (vehicleLoading) { if (vehicleLoading) {
return ( return (
<Layout> <>
<Head> <Head>
<title>Загрузка автомобиля...</title> <title>Загрузка автомобиля...</title>
</Head> </Head>
@ -117,14 +167,14 @@ const VehicleDetailsPage = () => {
<p className="mt-4 text-lg text-gray-600">Загружаем информацию об автомобиле...</p> <p className="mt-4 text-lg text-gray-600">Загружаем информацию об автомобиле...</p>
</div> </div>
</main> </main>
</Layout> </>
); );
} }
// Если информация о каталоге недоступна, показываем ошибку // Если информация о каталоге недоступна, показываем ошибку
if (!catalogData?.laximoCatalogInfo) { if (!catalogData?.laximoCatalogInfo) {
return ( return (
<Layout> <>
<Head> <Head>
<title>Каталог не найден</title> <title>Каталог не найден</title>
</Head> </Head>
@ -140,7 +190,7 @@ const VehicleDetailsPage = () => {
</button> </button>
</div> </div>
</main> </main>
</Layout> </>
); );
} }
@ -159,13 +209,84 @@ const VehicleDetailsPage = () => {
const catalogInfo = catalogData.laximoCatalogInfo; const catalogInfo = catalogData.laximoCatalogInfo;
return ( return (
<Layout> <>
<Head> <Head>
<title>{vehicleInfo.name} - Поиск запчастей</title> <title>VIN</title>
<meta name="description" content={`Поиск запчастей для ${vehicleInfo.name} в каталоге ${catalogInfo.name}`} /> <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> </Head>
<main className="min-h-screen bg-gray-50"> {/* ====== ВРЕМЕННЫЙ МАКЕТ ДЛЯ ВЕРСТКИ (начало) ====== */}
<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"> <nav className="bg-white border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -303,8 +424,7 @@ const VehicleDetailsPage = () => {
onSearchTypeChange={setSearchType} onSearchTypeChange={setSearchType}
/> />
</div> </div>
</main> </>
</Layout>
); );
}; };

View File

@ -38,10 +38,6 @@ export default function Vin() {
<meta content="vin" property="og:title" /> <meta content="vin" property="og:title" />
<meta content="vin" property="twitter:title" /> <meta content="vin" property="twitter:title" />
<meta content="width=device-width, initial-scale=1" name="viewport" /> <meta content="width=device-width, initial-scale=1" name="viewport" />
<meta content="Webflow" name="generator" />
<link href="/css/normalize.css" rel="stylesheet" type="text/css" />
<link href="/css/webflow.css" rel="stylesheet" type="text/css" />
<link href="/css/protekproject.webflow.css" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com" rel="preconnect" /> <link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" /> <link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" /> <link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />

View File

@ -404,3 +404,19 @@ input.input-receiver:focus {
.tabs-menu.w-tab-menu::-webkit-scrollbar { .tabs-menu.w-tab-menu::-webkit-scrollbar {
display: none; display: none;
} }
input.text-field,
input.w-input,
input#VinSearchInput {
background: #fff !important;
}
.text-block-56, .dropdown-link-3 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
max-width: 90%;
}

View File

@ -7458,31 +7458,43 @@ body {
} }
.flex-block-78 { .flex-block-78 {
flex-flow: row;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: center;
display: none;
}
.flex-block-80 {
display: none;
} }
.flex-block-81 { .flex-block-81 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: row;
justify-content: space-between; justify-content: space-between;
align-self: stretch;
align-items: center; align-items: center;
} }
.flex-block-82 { .core-product-copy {
grid-column-gap: 30px; flex-flow: column;
grid-row-gap: 30px;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: flex-start;
} }
.sort-item-brand-copy { .core-product-search-copy {
width: 50px; flex-flow: row;
align-self: stretch;
max-width: 100%;
height: 340px;
display: flex;
overflow: scroll;
}
.raiting-copy, .pcs-copy {
display: none;
}
.item-recommend-copy {
display: block;
}
.flex-block-83 {
flex-flow: column;
} }
.flex-block-84 { .flex-block-84 {
@ -7496,40 +7508,55 @@ body {
.flex-block-85 { .flex-block-85 {
grid-column-gap: 5px; grid-column-gap: 5px;
grid-row-gap: 5px; grid-row-gap: 5px;
border-radius: var(--_round---normal); border-radius: var(--_round---small-8);
background-color: var(--white); background-color: var(--white);
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
padding: 10px 20px; padding: 8px 12px;
display: flex; display: flex;
} }
.code-embed-9 { .code-embed-9 {
color: var(--_button---primary); color: var(--_button---primary);
width: 18px; width: 16px;
height: 18px; height: 16px;
} }
.flex-block-77-copy { .image-15 {
max-width: 200px;
}
.code-embed-10 {
color: var(--white);
width: 12px;
height: 16px;
}
.flex-block-86 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---small-8);
background-color: var(--_button---hover-dark_blue);
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
padding: 10px 15px;
} }
.flex-block-18-copy-copy { .flex-block-18-copy-copy {
grid-column-gap: 40px; grid-column-gap: 10px;
grid-row-gap: 40px; grid-row-gap: 10px;
flex-flow: column; }
.link-block-4-copy {
flex: 0 auto;
} }
.heading-8-copy { .heading-8-copy {
color: var(--_fonts---color--light-blue-grey);
font-size: var(--_fonts---font-size--small-font-size); font-size: var(--_fonts---font-size--small-font-size);
align-self: stretch; margin-left: 0;
padding-left: 0; margin-right: 0;
padding-right: 0;
font-weight: 400; font-weight: 400;
line-height: 20px;
display: block;
overflow: hidden;
} }
.dropdown-2 { .dropdown-2 {
@ -7537,95 +7564,98 @@ body {
margin-right: 0; margin-right: 0;
} }
.dropdown-list-2 {
background-color: var(--white);
box-shadow: 0 2px 5px #0003;
}
.dropdown-list-2.w--open {
border-radius: var(--_round---small-8);
}
.heading-9-copy { .heading-9-copy {
font-size: var(--_fonts---font-size--bigger); margin-top: 0;
} }
.info-block-search-copy { .info-block-search-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between; justify-content: space-between;
align-self: stretch; align-self: stretch;
align-items: center; align-items: flex-start;
}
.heading-9-copy-copy {
font-size: var(--_fonts---font-size--small-font-size);
line-height: 18px;
}
.section-2 {
padding-left: 15px;
padding-right: 15px;
} }
.mobile-block { .mobile-block {
display: flex; flex: 0 auto;
width: auto;
} }
.flex-block-87 { .flex-block-87 {
grid-column-gap: 10px; grid-column-gap: 0px;
grid-row-gap: 10px; grid-row-gap: 0px;
flex: 1;
} }
.mobile-menu-bottom { .mobile-menu-bottom {
padding-top: 0; margin-left: 0;
padding-bottom: 0; margin-right: 0;
box-shadow: 0 0 5px #0003; padding-left: 15px;
padding-right: 15px;
} }
.mobile-menu-bottom.info { .mobile-menu-bottom.nav, .mobile-menu-bottom.info {
padding-top: 20px; padding-left: 15px;
padding-bottom: 20px; padding-right: 15px;
} }
.mobile-menu-buttom-section { .mobile-menu-bottom.subscribe, .mobile-menu-bottom.footer {
display: block; padding: 40px 15px;
} }
.name-mobile-menu-item { .name-mobile-menu-item {
color: var(--black);
font-size: var(--_fonts---font-size--small-font-size);
font-weight: 400;
}
.button-for-mobile-menu-block {
grid-column-gap: 2px;
grid-row-gap: 2px;
background-color: var(--_fonts---color--white);
color: var(--_button---light-blue-grey);
flex-flow: column;
width: 70px;
}
.button-for-mobile-menu-block:hover {
background-color: var(--_button---light-blue);
}
.icon_favorite {
color: var(--_button---light-blue-grey);
}
.block-for-moble-menu-icon {
width: 30px;
height: 30px;
}
.div-block-25 {
width: 20px;
height: 20px;
display: block; display: block;
} }
.info-satus { .button-for-mobile-menu-block {
background-color: var(--green); grid-column-gap: 0px;
color: var(--_fonts---color--white); grid-row-gap: 0px;
font-size: 10px; width: 60px;
font-weight: 400; padding-bottom: 5px;
} }
.flex-block-93 { .section-3 {
align-self: auto; padding-left: 15px;
min-height: 48px; padding-right: 15px;
} }
.sort-list-card { .nav-menu-3 {
padding-right: 30px;
display: none; display: none;
} }
.flex-block-93 {
margin-left: 0;
}
.sort-list-card {
grid-column-gap: 0px;
grid-row-gap: 0px;
padding-left: 18px;
padding-right: 18px;
}
.flex-block-49-copy { .flex-block-49-copy {
grid-column-gap: 30px; grid-column-gap: 28px;
grid-row-gap: 30px; grid-row-gap: 28px;
} }
.price-in-cart-s1 { .price-in-cart-s1 {