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;
[key: string]: any;
};
onSearchResults?: (results: any[]) => void;
onSearchResults?: (data: {
results: any[];
loading: boolean;
error: any;
query: string;
isSearching?: boolean;
}) => void;
onNodeSelect?: (node: any) => void;
}
interface QuickGroup {
quickgroupid: string;
name: string;
link?: boolean;
children?: QuickGroup[];
}
const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, onNodeSelect }) => {
const catalogCode = vehicleInfo.catalog;
const vehicleId = vehicleInfo.vehicleid;
@ -79,13 +92,15 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
const searchResults = data?.laximoFulltextSearch;
useEffect(() => {
if (searchResults && onSearchResults) {
onSearchResults(searchResults.details || []);
if (onSearchResults) {
onSearchResults({
results: searchResults?.details || [],
loading: loading,
error: error,
query: searchQuery
});
}
if (!searchQuery.trim() && onSearchResults) {
onSearchResults([]);
}
}, [searchResults, searchQuery, onSearchResults]);
}, [searchResults, loading, error, searchQuery, onSearchResults]);
// --- Новый блок: вычисляем доступность поиска ---
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 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) ===
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 = () => {
if (!fulltextQuery.trim()) return;
if (!fulltextQuery.trim()) {
if (onSearchResults) {
onSearchResults({
results: [],
loading: false,
error: null,
query: '',
isSearching: false
});
}
return;
}
if (!ssd || ssd.trim() === '') {
console.error('SSD обязателен для поиска по названию');
return;
}
// Отправляем начальное состояние поиска родителю
if (onSearchResults) {
onSearchResults({
results: [],
loading: true,
error: null,
query: fulltextQuery.trim(),
isSearching: true
});
}
executeFulltextSearch({
variables: {
catalogCode,
vehicleId,
searchText: fulltextQuery.trim(),
searchQuery: fulltextQuery.trim(),
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>) => {
if (e.key === 'Enter') {
e.preventDefault();
@ -233,7 +258,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
<div className="div-block-2">
<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(); }}>
<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">
{/* 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"
required
value={fulltextQuery}
onChange={e => setFulltextQuery(e.target.value)}
onChange={handleInputChange}
onKeyDown={handleFulltextKeyDown}
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>
{(!ssd || ssd.trim() === '') && (
<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>
)}
{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 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) => {
const isOpen = openIndex === idx;
// Подкатегории: сначала children, если нет — unitsByCategory
const subcategories = category.children && category.children.length > 0
? category.children
: unitsByCategory[category.quickgroupid] || [];
@ -400,11 +358,11 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
data-delay="0"
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
>
<div
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
onClick={() => handleToggle(idx, category.quickgroupid)}
style={{ cursor: "pointer" }}
>
<div
className={`dropdown-toggle-card w-dropdown-toggle${isOpen ? " w--open" : ""}`}
onClick={() => handleToggle(idx, category.quickgroupid)}
style={{ cursor: "pointer" }}
>
<div className="w-icon-dropdown-toggle"></div>
<div className="text-block-56">{category.name}</div>
</div>
@ -414,7 +372,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
<a
href="#"
key={subcat.quickgroupid || subcat.unitid}
className="dropdown-link-3 w-dropdown-link"
className="dropdown-link-3 w-dropdown-link pl-0"
onClick={e => {
e.preventDefault();
if (onNodeSelect) {
@ -439,46 +397,158 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
)
) : (
// Manufacturer tab content (QuickGroups)
<div style={{ padding: '16px' }}>
{quickGroupsLoading ? (
<div style={{ textAlign: 'center' }}>Загружаем группы быстрого поиска...</div>
) : quickGroupsError ? (
<div style={{ color: 'red' }}>Ошибка загрузки групп: {quickGroupsError.message}</div>
) : selectedQuickGroup ? (
<div>
<button onClick={() => setSelectedQuickGroup(null)} className="mb-4 px-3 py-1 bg-gray-200 rounded">Назад к группам</button>
<h3 className="text-lg font-semibold mb-2">{selectedQuickGroup.name}</h3>
{quickDetailLoading ? (
<div>Загружаем детали...</div>
) : quickDetailError ? (
<div style={{ color: 'red' }}>Ошибка загрузки деталей: {quickDetailError.message}</div>
) : quickDetail && quickDetail.units && quickDetail.units.length > 0 ? (
<div className="space-y-3">
{quickDetail.units.map((unit: any) => (
<div key={unit.unitid} className="p-3 bg-gray-50 rounded border border-gray-200">
<div className="font-medium text-gray-900">{unit.name}</div>
{unit.details && unit.details.length > 0 && (
<ul className="mt-2 text-sm text-gray-700 list-disc pl-5">
{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>
))}
</ul>
)}
</div>
))}
quickGroupsLoading ? (
<div style={{ padding: 16, textAlign: 'center' }}>Загружаем группы быстрого поиска...</div>
) : quickGroupsError ? (
<div style={{ color: 'red', padding: 16 }}>Ошибка загрузки групп: {quickGroupsError.message}</div>
) : (
<>
{(quickGroups as QuickGroup[]).map((group: QuickGroup) => {
const hasChildren = group.children && group.children.length > 0;
const isOpen = expandedQuickGroups.has(group.quickgroupid);
if (!hasChildren) {
return (
<a
href="#"
key={group.quickgroupid}
className="dropdown-link-3 w-dropdown-link"
onClick={(e) => {
e.preventDefault();
if (group.link) {
handleQuickGroupClick(group);
}
}}
>
{group.name}
</a>
);
}
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>
) : quickGroups.length > 0 ? (
renderQuickGroupTree(quickGroups)
) : (
<div>Нет доступных групп быстрого поиска</div>
)}
</div>
);
})}
{/* Quick Detail Modal */}
{selectedQuickGroup && (
<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 className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold">{selectedQuickGroup.name}</h3>
<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 */}
</div>