This commit is contained in:
egortriston
2025-06-30 14:52:30 +03:00
parent 8cae029d7f
commit 69ccc786ea
5 changed files with 336 additions and 194 deletions

View File

@ -10,10 +10,23 @@ interface VinLeftbarProps {
ssd: string; ssd: string;
[key: string]: any; [key: string]: any;
}; };
onSearchResults?: (results: any[]) => void; onSearchResults?: (data: {
results: any[];
loading: boolean;
error: any;
query: string;
isSearching?: boolean;
}) => void;
onNodeSelect?: (node: any) => void; onNodeSelect?: (node: any) => void;
} }
interface QuickGroup {
quickgroupid: string;
name: string;
link?: boolean;
children?: QuickGroup[];
}
const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, onNodeSelect }) => { const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, onNodeSelect }) => {
const catalogCode = vehicleInfo.catalog; const catalogCode = vehicleInfo.catalog;
const vehicleId = vehicleInfo.vehicleid; const vehicleId = vehicleInfo.vehicleid;
@ -79,13 +92,15 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
const searchResults = data?.laximoFulltextSearch; const searchResults = data?.laximoFulltextSearch;
useEffect(() => { useEffect(() => {
if (searchResults && onSearchResults) { if (onSearchResults) {
onSearchResults(searchResults.details || []); onSearchResults({
results: searchResults?.details || [],
loading: loading,
error: error,
query: searchQuery
});
} }
if (!searchQuery.trim() && onSearchResults) { }, [searchResults, loading, error, searchQuery, onSearchResults]);
onSearchResults([]);
}
}, [searchResults, searchQuery, onSearchResults]);
// --- Новый блок: вычисляем доступность поиска --- // --- Новый блок: вычисляем доступность поиска ---
const isSearchAvailable = !!catalogCode && vehicleId !== undefined && vehicleId !== null && !!ssd && ssd.trim() !== ''; const isSearchAvailable = !!catalogCode && vehicleId !== undefined && vehicleId !== null && !!ssd && ssd.trim() !== '';
@ -170,54 +185,64 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
}); });
const quickDetail = quickDetailData?.laximoQuickDetail; const quickDetail = quickDetailData?.laximoQuickDetail;
const renderQuickGroupTree = (groups: any[], level = 0): React.ReactNode => (
<div>
{groups.map(group => (
<div key={group.quickgroupid} style={{ marginLeft: level * 16, marginBottom: 8 }}>
<div
className={`flex items-center p-2 rounded cursor-pointer border ${group.link ? 'bg-white hover:bg-red-50 border-gray-200 hover:border-red-300' : 'bg-gray-50 hover:bg-gray-100 border-gray-200'}`}
onClick={() => handleQuickGroupClick(group)}
>
{group.children && group.children.length > 0 && (
<svg className={`w-4 h-4 text-gray-400 mr-2 transition-transform ${expandedQuickGroups.has(group.quickgroupid) ? 'rotate-90' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
)}
<span className={`font-medium ${group.link ? 'text-gray-900' : 'text-gray-600'}`}>{group.name}</span>
{group.link && (
<span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">Доступен поиск</span>
)}
</div>
{group.children && group.children.length > 0 && expandedQuickGroups.has(group.quickgroupid) && (
<div className="mt-1">
{renderQuickGroupTree(group.children, level + 1)}
</div>
)}
</div>
))}
</div>
);
// === Полнотекстовый поиск деталей (аналогично FulltextSearchSection) === // === Полнотекстовый поиск деталей (аналогично FulltextSearchSection) ===
const [fulltextQuery, setFulltextQuery] = useState(''); const [fulltextQuery, setFulltextQuery] = useState('');
const [executeFulltextSearch, { data: fulltextData, loading: fulltextLoading, error: fulltextError }] = useLazyQuery(SEARCH_LAXIMO_FULLTEXT, { errorPolicy: 'all' }); const [executeFulltextSearch, { data: fulltextData, loading: fulltextLoading, error: fulltextError }] = useLazyQuery(GET_LAXIMO_FULLTEXT_SEARCH, { errorPolicy: 'all' });
const handleFulltextSearch = () => { const handleFulltextSearch = () => {
if (!fulltextQuery.trim()) return; if (!fulltextQuery.trim()) {
if (onSearchResults) {
onSearchResults({
results: [],
loading: false,
error: null,
query: '',
isSearching: false
});
}
return;
}
if (!ssd || ssd.trim() === '') { if (!ssd || ssd.trim() === '') {
console.error('SSD обязателен для поиска по названию'); console.error('SSD обязателен для поиска по названию');
return; return;
} }
// Отправляем начальное состояние поиска родителю
if (onSearchResults) {
onSearchResults({
results: [],
loading: true,
error: null,
query: fulltextQuery.trim(),
isSearching: true
});
}
executeFulltextSearch({ executeFulltextSearch({
variables: { variables: {
catalogCode, catalogCode,
vehicleId, vehicleId,
searchText: fulltextQuery.trim(), searchQuery: fulltextQuery.trim(),
ssd ssd
} }
}); });
}; };
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setFulltextQuery(newValue);
};
useEffect(() => {
if (onSearchResults && (fulltextData || fulltextLoading || fulltextError)) {
onSearchResults({
results: fulltextData?.laximoFulltextSearch?.details || [],
loading: fulltextLoading,
error: fulltextError,
query: fulltextQuery,
isSearching: true
});
}
}, [fulltextData, fulltextLoading, fulltextError, onSearchResults]);
const handleFulltextKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleFulltextKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
@ -233,7 +258,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
<div className="div-block-2"> <div className="div-block-2">
<div className="form-block w-form"> <div className="form-block w-form">
<form id="vin-form-search" name="vin-form-search" data-name="vin-form-search" action="#" method="post" className="form" onSubmit={e => { e.preventDefault(); handleFulltextSearch(); }}> <form id="vin-form-search" name="vin-form-search" data-name="vin-form-search" action="#" method="post" className="form" onSubmit={e => { e.preventDefault(); handleFulltextSearch(); }}>
<a href="#" className="link-block-3 w-inline-block" onClick={e => { e.preventDefault(); if (!ssd || ssd.trim() === '') { return; } handleFulltextSearch(); }}> <a href="#" className="link-block-3 w-inline-block" onClick={e => { e.preventDefault(); handleFulltextSearch(); }}>
<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">
@ -252,18 +277,10 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
id="VinSearchInput" id="VinSearchInput"
required required
value={fulltextQuery} value={fulltextQuery}
onChange={e => setFulltextQuery(e.target.value)} onChange={handleInputChange}
onKeyDown={handleFulltextKeyDown} onKeyDown={handleFulltextKeyDown}
disabled={fulltextLoading} disabled={fulltextLoading}
/> />
<button
type="button"
onClick={handleFulltextSearch}
disabled={!fulltextQuery.trim() || fulltextLoading || !ssd || ssd.trim() === ''}
className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors ml-2"
>
{fulltextLoading ? 'Поиск...' : 'Найти'}
</button>
</form> </form>
{(!ssd || ssd.trim() === '') && ( {(!ssd || ssd.trim() === '') && (
<div className="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded-lg"> <div className="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
@ -282,64 +299,6 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
</div> </div>
</div> </div>
)} )}
{fulltextError && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mt-3">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
Ошибка поиска
</h3>
<div className="mt-2 text-sm text-red-700">
<p>{fulltextError.message}</p>
</div>
</div>
</div>
</div>
)}
{fulltextResults && (
<div className="bg-white border border-gray-200 rounded-lg overflow-hidden mt-3">
<div className="px-6 py-4 bg-gray-50 border-b border-gray-200">
<h3 className="text-lg font-medium text-gray-900">
Результаты поиска: "{fulltextQuery}"
</h3>
<p className="text-sm text-gray-600 mt-1">
Найдено {fulltextResults.length} деталей
</p>
</div>
{fulltextResults.length > 0 ? (
<div className="space-y-4 p-6">
<div className="text-sm text-blue-700 bg-blue-50 border border-blue-200 rounded-lg p-3">
💡 Нажмите на карточку детали для поиска предложений и цен. Используйте кнопку "Показать применимость" для просмотра применения в автомобиле.
</div>
{fulltextResults.map((detail: any, index: number) => (
<VinPartCard
key={`${detail.oem}-${index}`}
n={index + 1}
name={detail.name}
oem={detail.oem}
/>
))}
</div>
) : (
<div className="px-6 py-8 text-center">
<svg className="w-12 h-12 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p className="text-gray-600">
По запросу "{fulltextQuery}" ничего не найдено
</p>
<p className="text-sm text-gray-500 mt-1">
Попробуйте изменить поисковый запрос
</p>
</div>
)}
</div>
)}
</div> </div>
</div> </div>
<div className="w-layout-vflex flex-block-113"> <div className="w-layout-vflex flex-block-113">
@ -389,7 +348,6 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
<> <>
{categories.map((category: any, idx: number) => { {categories.map((category: any, idx: number) => {
const isOpen = openIndex === idx; const isOpen = openIndex === idx;
// Подкатегории: сначала children, если нет — unitsByCategory
const subcategories = category.children && category.children.length > 0 const subcategories = category.children && category.children.length > 0
? category.children ? category.children
: unitsByCategory[category.quickgroupid] || []; : unitsByCategory[category.quickgroupid] || [];
@ -400,11 +358,11 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
data-delay="0" data-delay="0"
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`} className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
> >
<div <div
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`} className={`dropdown-toggle-card w-dropdown-toggle${isOpen ? " w--open" : ""}`}
onClick={() => handleToggle(idx, category.quickgroupid)} onClick={() => handleToggle(idx, category.quickgroupid)}
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
> >
<div className="w-icon-dropdown-toggle"></div> <div className="w-icon-dropdown-toggle"></div>
<div className="text-block-56">{category.name}</div> <div className="text-block-56">{category.name}</div>
</div> </div>
@ -414,7 +372,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
<a <a
href="#" href="#"
key={subcat.quickgroupid || subcat.unitid} key={subcat.quickgroupid || subcat.unitid}
className="dropdown-link-3 w-dropdown-link" className="dropdown-link-3 w-dropdown-link pl-0"
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
if (onNodeSelect) { if (onNodeSelect) {
@ -439,46 +397,158 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
) )
) : ( ) : (
// Manufacturer tab content (QuickGroups) // Manufacturer tab content (QuickGroups)
<div style={{ padding: '16px' }}> quickGroupsLoading ? (
{quickGroupsLoading ? ( <div style={{ padding: 16, textAlign: 'center' }}>Загружаем группы быстрого поиска...</div>
<div style={{ textAlign: 'center' }}>Загружаем группы быстрого поиска...</div> ) : quickGroupsError ? (
) : quickGroupsError ? ( <div style={{ color: 'red', padding: 16 }}>Ошибка загрузки групп: {quickGroupsError.message}</div>
<div style={{ color: 'red' }}>Ошибка загрузки групп: {quickGroupsError.message}</div> ) : (
) : selectedQuickGroup ? ( <>
<div> {(quickGroups as QuickGroup[]).map((group: QuickGroup) => {
<button onClick={() => setSelectedQuickGroup(null)} className="mb-4 px-3 py-1 bg-gray-200 rounded">Назад к группам</button> const hasChildren = group.children && group.children.length > 0;
<h3 className="text-lg font-semibold mb-2">{selectedQuickGroup.name}</h3> const isOpen = expandedQuickGroups.has(group.quickgroupid);
{quickDetailLoading ? (
<div>Загружаем детали...</div> if (!hasChildren) {
) : quickDetailError ? ( return (
<div style={{ color: 'red' }}>Ошибка загрузки деталей: {quickDetailError.message}</div> <a
) : quickDetail && quickDetail.units && quickDetail.units.length > 0 ? ( href="#"
<div className="space-y-3"> key={group.quickgroupid}
{quickDetail.units.map((unit: any) => ( className="dropdown-link-3 w-dropdown-link"
<div key={unit.unitid} className="p-3 bg-gray-50 rounded border border-gray-200"> onClick={(e) => {
<div className="font-medium text-gray-900">{unit.name}</div> e.preventDefault();
{unit.details && unit.details.length > 0 && ( if (group.link) {
<ul className="mt-2 text-sm text-gray-700 list-disc pl-5"> handleQuickGroupClick(group);
{unit.details.map((detail: any) => ( }
<li key={detail.detailid}> }}
<span className="font-medium">{detail.name}</span> <span className="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded">OEM: {detail.oem}</span> >
</li> {group.name}
))} </a>
</ul> );
)} }
</div>
))} return (
<div
key={group.quickgroupid}
data-hover="false"
data-delay="0"
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
>
<div
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
onClick={() => handleQuickGroupToggle(group.quickgroupid)}
style={{ cursor: "pointer" }}
>
<div className="w-icon-dropdown-toggle"></div>
<div className="text-block-56">{group.name}</div>
</div>
<nav className={`dropdown-list-4 w-dropdown-list${isOpen ? " w--open" : ""}`}>
{group.children?.map((child: QuickGroup) => {
const hasSubChildren = child.children && child.children.length > 0;
const isChildOpen = expandedQuickGroups.has(child.quickgroupid);
if (!hasSubChildren) {
return (
<a
href="#"
key={child.quickgroupid}
className="dropdown-link-3 w-dropdown-link "
onClick={(e) => {
e.preventDefault();
if (child.link) {
handleQuickGroupClick(child);
}
}}
>
{child.name}
</a>
);
}
return (
<div
key={child.quickgroupid}
data-hover="false"
data-delay="0"
className={`dropdown-4 w-dropdown pl-0${isChildOpen ? " w--open" : ""}`}
>
<div
className={`dropdown-toggle-card w-dropdown-toggle pl-0${isChildOpen ? " w--open" : ""}`}
onClick={() => handleQuickGroupToggle(child.quickgroupid)}
style={{ cursor: "pointer" }}
>
<div className="w-icon-dropdown-toggle"></div>
<div className="text-block-56">{child.name}</div>
</div>
<nav className={`dropdown-list-4 w-dropdown-list pl-0${isChildOpen ? " w--open" : ""}`}>
{child.children?.map((subChild: QuickGroup) => (
<a
href="#"
key={subChild.quickgroupid}
className="dropdown-link-3 w-dropdown-link "
onClick={(e) => {
e.preventDefault();
if (subChild.link) {
handleQuickGroupClick(subChild);
}
}}
>
{subChild.name}
</a>
))}
</nav>
</div>
);
})}
</nav>
</div> </div>
) : ( );
<div>Нет деталей для этой группы</div> })}
)}
</div> {/* Quick Detail Modal */}
) : quickGroups.length > 0 ? ( {selectedQuickGroup && (
renderQuickGroupTree(quickGroups) <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
) : ( <div className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto p-6">
<div>Нет доступных групп быстрого поиска</div> <div className="flex justify-between items-center mb-4">
)} <h3 className="text-lg font-semibold">{selectedQuickGroup.name}</h3>
</div> <button
onClick={() => setSelectedQuickGroup(null)}
className="text-gray-500 hover:text-gray-700"
>
</button>
</div>
{quickDetailLoading ? (
<div className="text-center py-4">Загружаем детали...</div>
) : quickDetailError ? (
<div className="text-red-600 py-4">Ошибка загрузки деталей: {quickDetailError.message}</div>
) : quickDetail?.units?.length > 0 ? (
<div className="space-y-4">
{quickDetail.units.map((unit: any) => (
<div key={unit.unitid} className="border border-gray-200 rounded-lg p-4">
<div className="font-medium text-gray-900 mb-2">{unit.name}</div>
{unit.details && unit.details.length > 0 && (
<div className="space-y-2">
{unit.details.map((detail: any) => (
<div key={detail.detailid} className="flex items-center justify-between bg-gray-50 p-2 rounded">
<span className="font-medium text-gray-700">{detail.name}</span>
<span className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
OEM: {detail.oem}
</span>
</div>
))}
</div>
)}
</div>
))}
</div>
) : (
<div className="text-center text-gray-500 py-4">Нет деталей для этой группы</div>
)}
</div>
</div>
)}
</>
)
)} )}
{/* Tab content end */} {/* Tab content end */}
</div> </div>

View File

@ -1,5 +1,6 @@
import React from "react"; import React, { useState } from "react";
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import BrandSelectionModal from '../BrandSelectionModal';
interface VinPartCardProps { interface VinPartCardProps {
n?: number; n?: number;
@ -10,28 +11,38 @@ interface VinPartCardProps {
const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick }) => { const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick }) => {
const router = useRouter(); const router = useRouter();
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
const handlePriceClick = (e: React.MouseEvent) => { const handlePriceClick = (e: React.MouseEvent) => {
e.preventDefault(); e.preventDefault();
if (onPriceClick) onPriceClick(); if (onPriceClick) onPriceClick();
if (oem) router.push(`/search?q=${encodeURIComponent(oem)}&mode=parts`); setIsBrandModalOpen(true);
}; };
return ( return (
<div className="w-layout-hflex knotlistitem"> <>
<div className="w-layout-hflex flex-block-116"> <div className="w-layout-hflex knotlistitem">
{n !== undefined && <div className="nuberlist">{n}</div>} <div className="w-layout-hflex flex-block-116">
<div className="oemnuber">{oem}</div> {n !== undefined && <div className="nuberlist">{n}</div>}
</div> <div className="oemnuber">{oem}</div>
<div className="partsname">{name}</div> </div>
<div className="w-layout-hflex flex-block-117"> <div className="partsname">{name}</div>
<a href="#" className="button-3 w-button" onClick={handlePriceClick}>Цена</a> <div className="w-layout-hflex flex-block-117">
<div className="code-embed-16 w-embed"> <a href="#" className="button-3 w-button" onClick={handlePriceClick}>Цена</a>
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <div className="code-embed-16 w-embed">
<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 width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
</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> <BrandSelectionModal
isOpen={isBrandModalOpen}
onClose={() => setIsBrandModalOpen(false)}
articleNumber={oem}
detailName={name}
/>
</>
); );
}; };

View File

@ -53,6 +53,17 @@ const VehicleDetailsPage = () => {
const [searchType, setSearchType] = useState<'quickgroups' | 'categories' | 'fulltext'>(defaultSearchType); const [searchType, setSearchType] = useState<'quickgroups' | 'categories' | 'fulltext'>(defaultSearchType);
const [showKnot, setShowKnot] = useState(false); const [showKnot, setShowKnot] = useState(false);
const [foundParts, setFoundParts] = useState<any[]>([]); const [foundParts, setFoundParts] = useState<any[]>([]);
const [searchState, setSearchState] = useState<{
loading: boolean;
error: any;
query: string;
isSearching: boolean;
}>({
loading: false,
error: null,
query: '',
isSearching: false
});
const [selectedNode, setSelectedNode] = useState<any | null>(null); const [selectedNode, setSelectedNode] = useState<any | null>(null);
const handleCategoryClick = (e?: React.MouseEvent) => { const handleCategoryClick = (e?: React.MouseEvent) => {
if (e) e.preventDefault(); if (e) e.preventDefault();
@ -272,33 +283,74 @@ const VehicleDetailsPage = () => {
{!selectedNode ? ( {!selectedNode ? (
<div className="w-layout-hflex flex-block-13"> <div className="w-layout-hflex flex-block-13">
{vehicleInfo && vehicleInfo.catalog && vehicleInfo.vehicleid && vehicleInfo.ssd && ( {vehicleInfo && vehicleInfo.catalog && vehicleInfo.vehicleid && vehicleInfo.ssd && (
<VinLeftbar <>
vehicleInfo={vehicleInfo} <VinLeftbar
onSearchResults={setFoundParts} vehicleInfo={vehicleInfo}
onNodeSelect={setSelectedNode} onSearchResults={({ results, loading, error, query, isSearching }) => {
/> setFoundParts(results);
)} setSearchState({ loading, error, query, isSearching: isSearching || false });
{/* Категории или Knot или карточки */} }}
{foundParts.length > 0 ? ( onNodeSelect={setSelectedNode}
<div className="knot-parts"> />
{foundParts.map((detail, idx) => ( {searchState.isSearching ? (
<VinPartCard <div className="knot-parts">
key={detail.oem + idx} {searchState.loading ? (
n={idx + 1} <div className="text-center py-12">
name={detail.name} <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-red-600 mx-auto"></div>
oem={detail.oem} <p className="mt-4 text-gray-600">Выполняется поиск...</p>
</div>
) : searchState.error ? (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mt-3">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
Ошибка поиска
</h3>
<div className="mt-2 text-sm text-red-700">
<p>{searchState.error.message}</p>
</div>
</div>
</div>
</div>
) : foundParts.length > 0 ? (
foundParts.map((detail, idx) => (
<VinPartCard
key={detail.oem + idx}
n={idx + 1}
name={detail.name}
oem={detail.oem}
/>
))
) : (
<div className="text-center py-12">
<svg className="w-12 h-12 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p className="text-gray-600">
По запросу "{searchState.query}" ничего не найдено
</p>
<p className="text-sm text-gray-500 mt-1">
Попробуйте изменить поисковый запрос
</p>
</div>
)}
</div>
) : showKnot ? (
<VinKnot />
) : (
<VinCategory
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
ssd={vehicleInfo.ssd}
onNodeSelect={setSelectedNode}
/> />
))} )}
</div> </>
) : showKnot ? (
<VinKnot />
) : (
<VinCategory
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
ssd={vehicleInfo.ssd}
onNodeSelect={setSelectedNode}
/>
)} )}
</div> </div>
) : ( ) : (

View File

@ -465,4 +465,13 @@ input#VinSearchInput {
grid-template-areas: none !important; grid-template-areas: none !important;
grid-auto-flow: row !important; /* <--- ВАЖНО! */ grid-auto-flow: row !important; /* <--- ВАЖНО! */
} }
}
.dropdown-toggle-card {
padding-left: 0 !important;
}
.dropdown-link-3 {
margin-left: 0 !important;
} }

View File

@ -9486,7 +9486,7 @@ body {
display: flex; display: flex;
} }
.dropdown-toggle-3 { .dropdown-toggle-3, .dropdown-toggle-card {
border-top-right-radius: var(--_round---normal); border-top-right-radius: var(--_round---normal);
border-bottom-right-radius: var(--_round---normal); border-bottom-right-radius: var(--_round---normal);
border-left: 2px solid #0000; border-left: 2px solid #0000;