0207
This commit is contained in:
@ -14,6 +14,8 @@ const BrandWizardSearchSection: React.FC = () => {
|
||||
const [vehicles, setVehicles] = useState<LaximoVehicleSearchResult[] | null>(null);
|
||||
const [brandQuery, setBrandQuery] = useState('');
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
// Получение информации о каталоге через useQuery
|
||||
const {
|
||||
@ -68,7 +70,7 @@ const BrandWizardSearchSection: React.FC = () => {
|
||||
const catalogInfo = catalogData?.laximoCatalogInfo;
|
||||
|
||||
return (
|
||||
<section className="max-w-[1100px] min-h-[450px] mx-auto bg-white rounded-2xl shadow p-6 md:p-10 my-8">
|
||||
<section className="max-w-[1580px] min-h-[450px] mx-auto bg-white rounded-2xl shadow p-6 md:p-10 my-8">
|
||||
{/* <div className="text-2xl font-bold text-gray-900 mb-6 mt-6 text-center" style={{ fontSize: '28px' }}>Подбор автомобиля по параметрам</div> */}
|
||||
{/* Combobox бренда */}
|
||||
<div className="mb-8 w-full">
|
||||
@ -82,45 +84,64 @@ const BrandWizardSearchSection: React.FC = () => {
|
||||
</div>
|
||||
<Combobox value={selectedBrand} onChange={handleBrandChange} nullable>
|
||||
<div className="relative">
|
||||
{/* Невидимая кнопка поверх инпута */}
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-0 left-0 w-full h-full opacity-0 z-10 cursor-pointer"
|
||||
tabIndex={0}
|
||||
aria-label="Открыть список брендов"
|
||||
onClick={() => {
|
||||
inputRef.current?.focus();
|
||||
if (inputRef.current) {
|
||||
inputRef.current.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Combobox.Input
|
||||
ref={inputRef}
|
||||
id="brand-combobox"
|
||||
className="w-full px-6 py-4 bg-white rounded border border-stone-300 text-sm text-gray-950 placeholder:text-neutral-500 outline-none focus:shadow-none focus:border-stone-300 transition-colors"
|
||||
displayValue={(brand: LaximoBrand | null) => brand?.name || ''}
|
||||
onChange={e => setBrandQuery(e.target.value)}
|
||||
placeholder="Начните вводить бренд..."
|
||||
autoComplete="off"
|
||||
onFocus={() => setIsOpen(true)}
|
||||
onClick={() => setIsOpen(true)}
|
||||
onBlur={() => setIsOpen(false)}
|
||||
/>
|
||||
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center px-3 focus:outline-none w-12">
|
||||
<Combobox.Button ref={buttonRef} className="absolute inset-y-0 right-0 flex items-center px-3 focus:outline-none w-12">
|
||||
<svg className="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</Combobox.Button>
|
||||
<Combobox.Options
|
||||
className="absolute left-0 top-full z-10 bg-white border-x border-b border-stone-300 rounded-b-lg shadow-lg w-full max-h-60 overflow-auto scrollbar-none"
|
||||
style={{ scrollbarWidth: 'none' }}
|
||||
data-hide-scrollbar
|
||||
>
|
||||
{brandsLoading && (
|
||||
<div className="px-6 py-4 text-gray-500">Загрузка брендов...</div>
|
||||
)}
|
||||
{brandsError && (
|
||||
<div className="px-6 py-4 text-red-500">Ошибка загрузки брендов</div>
|
||||
)}
|
||||
{filteredBrands.length === 0 && !brandsLoading && !brandsError && (
|
||||
<div className="px-6 py-4 text-gray-500">Бренды не найдены</div>
|
||||
)}
|
||||
{filteredBrands.map(brand => (
|
||||
<Combobox.Option
|
||||
key={brand.code}
|
||||
value={brand}
|
||||
className={({ active, selected }) =>
|
||||
`px-6 py-4 cursor-pointer hover:!bg-[rgb(236,28,36)] hover:!text-white text-sm transition-colors ${selected ? 'bg-red-50 font-semibold text-gray-950' : 'text-neutral-500'}`
|
||||
}
|
||||
>
|
||||
{brand.name}
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
{isOpen && (
|
||||
<Combobox.Options
|
||||
className="absolute left-0 top-full z-10 bg-white border-x border-b border-stone-300 rounded-b-lg shadow-lg w-full max-h-60 overflow-auto scrollbar-none"
|
||||
style={{ scrollbarWidth: 'none' }}
|
||||
data-hide-scrollbar
|
||||
>
|
||||
{brandsLoading && (
|
||||
<div className="px-6 py-4 text-gray-500">Загрузка брендов...</div>
|
||||
)}
|
||||
{brandsError && (
|
||||
<div className="px-6 py-4 text-red-500">Ошибка загрузки брендов</div>
|
||||
)}
|
||||
{filteredBrands.length === 0 && !brandsLoading && !brandsError && (
|
||||
<div className="px-6 py-4 text-gray-500">Бренды не найдены</div>
|
||||
)}
|
||||
{filteredBrands.map(brand => (
|
||||
<Combobox.Option
|
||||
key={brand.code}
|
||||
value={brand}
|
||||
className={({ active, selected }) =>
|
||||
`px-6 py-4 cursor-pointer hover:!bg-[rgb(236,28,36)] hover:!text-white text-sm transition-colors ${selected ? 'bg-red-50 font-semibold text-gray-950' : 'text-neutral-500'}`
|
||||
}
|
||||
>
|
||||
{brand.name}
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
)}
|
||||
</div>
|
||||
</Combobox>
|
||||
</div>
|
||||
@ -152,7 +173,7 @@ const BrandWizardSearchSection: React.FC = () => {
|
||||
</>
|
||||
)}
|
||||
{catalogInfo && !catalogInfo.supportparameteridentification2 && (
|
||||
<div className="text-yellow-700 bg-yellow-50 border border-yellow-200 rounded-lg p-4 mt-6 text-center">
|
||||
<div className="text-blue-700 bg-blue-50 border border-blue-200 rounded-lg p-4 mt-6 text-center">
|
||||
Для выбранного бренда подбор по параметрам недоступен.
|
||||
</div>
|
||||
)}
|
||||
|
@ -63,7 +63,7 @@ const VehicleSearchResults: React.FC<VehicleSearchResultsProps> = ({
|
||||
{results.map((vehicle, index) => (
|
||||
<div
|
||||
key={vehicle.vehicleid || index}
|
||||
className="pt-3 pb-3 bg-white border-b border-gray-200 hover:bg-neutral-50 transition-colors cursor-pointer flex flex-col sm:flex-row sm:items-center gap-4"
|
||||
className="pt-3 pb-3 px-5 bg-white border-b border-gray-200 hover:bg-neutral-50 transition-colors cursor-pointer flex flex-col sm:flex-row sm:items-center gap-4"
|
||||
onClick={() => handleSelectVehicle(vehicle)}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
@ -100,15 +100,15 @@ const VehicleSearchResults: React.FC<VehicleSearchResultsProps> = ({
|
||||
</div>
|
||||
|
||||
{vehicle.notes && (
|
||||
<div className="mt-3 p-3 bg-yellow-50 rounded-lg">
|
||||
<div className="mt-3 p-3 bg-blue-50 rounded-lg">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<svg className="h-5 w-5 text-blue-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">
|
||||
<p className="text-sm text-yellow-800">
|
||||
<p className="text-sm text-blue-800">
|
||||
<span className="font-medium">Примечание:</span> {vehicle.notes}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -20,6 +20,7 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
|
||||
const [error, setError] = useState<string>('');
|
||||
const [queries, setQueries] = useState<Record<string, string>>({});
|
||||
const buttonRefs = useRef<Record<string, React.RefObject<HTMLButtonElement | null>>>({});
|
||||
const inputRefs = useRef<Record<string, React.RefObject<HTMLInputElement>>>({});
|
||||
const [showSearchButton, setShowSearchButton] = React.useState(true);
|
||||
|
||||
const [getWizard2] = useLazyQuery(GET_LAXIMO_WIZARD2, {
|
||||
@ -220,14 +221,18 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
|
||||
? options.filter(option => option.value.toLowerCase().includes(query.toLowerCase()))
|
||||
: options;
|
||||
const buttonRef = buttonRefs.current[step.conditionid];
|
||||
// Создаём ref для инпута, если его ещё нет
|
||||
if (!inputRefs.current[step.conditionid]) {
|
||||
inputRefs.current[step.conditionid] = React.createRef<HTMLInputElement>();
|
||||
}
|
||||
const inputRef = inputRefs.current[step.conditionid];
|
||||
// Определяем выбранный ключ
|
||||
const selectedKey = selectedParams[step.conditionid]?.key || (step.determined ? options.find(o => o.value === step.value)?.key : '');
|
||||
// Определяем отображаемый label
|
||||
const selectedLabel =
|
||||
options.find(o => o.key === selectedKey)?.value ||
|
||||
selectedParams[step.conditionid]?.value ||
|
||||
step.value ||
|
||||
'';
|
||||
step.value || '';
|
||||
|
||||
// Если единственный вариант уже выбран — не рендерим селект
|
||||
if (options.length === 1 && (selectedKey === options[0].key || step.determined)) {
|
||||
@ -252,7 +257,21 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
|
||||
disabled={isLoading || options.length === 0}
|
||||
>
|
||||
<div className="relative">
|
||||
{/* Невидимая кнопка поверх инпута */}
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-0 left-0 w-full h-full opacity-0 z-10 cursor-pointer"
|
||||
tabIndex={0}
|
||||
aria-label="Открыть список опций"
|
||||
onClick={() => {
|
||||
inputRef.current?.focus();
|
||||
if (inputRef.current) {
|
||||
inputRef.current.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Combobox.Input
|
||||
ref={inputRef}
|
||||
id={`wizard-combobox-${step.conditionid}`}
|
||||
className={`w-full px-6 py-4 rounded text-sm text-gray-950 placeholder:text-neutral-500 outline-none focus:shadow-none transition-colors pr-12 ${selectedLabel ? 'bg-gray-50 border-gray-200' : 'bg-white border border-stone-300'}`}
|
||||
displayValue={() => selectedLabel}
|
||||
@ -264,7 +283,7 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
|
||||
{selectedLabel ? (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-y-0 right-0 w-12 flex items-center justify-center text-gray-400 hover:text-red-600 focus:outline-none"
|
||||
className="absolute inset-y-0 right-0 w-12 flex items-center justify-center text-gray-400 hover:text-red-600 focus:outline-none z-10"
|
||||
aria-label="Сбросить"
|
||||
tabIndex={0}
|
||||
onClick={() => handleParamReset(step)}
|
||||
@ -330,8 +349,8 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
|
||||
|
||||
{/* Информация о недостаточности параметров */}
|
||||
{!isLoading && !canListVehicles && wizardSteps.length > 0 && (
|
||||
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p className="text-yellow-800 text-sm">
|
||||
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<p className="text-blue-800 text-sm">
|
||||
Выберите больше параметров для поиска автомобилей
|
||||
</p>
|
||||
</div>
|
||||
|
@ -123,9 +123,9 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
|
||||
<>
|
||||
<div className="relative inline-block">
|
||||
{/* ВРЕМЕННО: выводим количество точек для быстрой проверки */}
|
||||
<div style={{ position: 'absolute', top: 4, left: 4, zIndex: 20, background: 'rgba(255,0,0,0.1)', color: '#c00', fontWeight: 700, fontSize: 14, padding: '2px 8px', borderRadius: 6 }}>
|
||||
{/* <div style={{ position: 'absolute', top: 4, left: 4, zIndex: 20, background: 'rgba(255,0,0,0.1)', color: '#c00', fontWeight: 700, fontSize: 14, padding: '2px 8px', borderRadius: 6 }}>
|
||||
{coordinates.length} точек
|
||||
</div>
|
||||
</div> */}
|
||||
<img
|
||||
ref={imgRef}
|
||||
src={imageUrl}
|
||||
|
@ -165,7 +165,7 @@ const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd,
|
||||
) : (
|
||||
// Список подкатегорий
|
||||
<>
|
||||
<div className="div-block-131" onClick={handleBack} style={{ cursor: "pointer", fontWeight: 500 }}>
|
||||
{/* <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">
|
||||
@ -173,7 +173,7 @@ const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd,
|
||||
<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> */}
|
||||
{subcategories.length === 0 && <div style={{ color: "#888", padding: 8 }}>Нет подкатегорий</div>}
|
||||
{subcategories.map((subcat: any, idx: number) => (
|
||||
<div
|
||||
|
@ -118,18 +118,16 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
});
|
||||
const quickGroups = quickGroupsData?.laximoQuickGroups || [];
|
||||
|
||||
const [expandedQuickGroups, setExpandedQuickGroups] = useState<Set<string>>(new Set());
|
||||
const [expandedQuickGroup, setExpandedQuickGroup] = useState<string | null>(null);
|
||||
const [expandedSubQuickGroup, setExpandedSubQuickGroup] = useState<string | null>(null);
|
||||
|
||||
const handleQuickGroupToggle = (groupId: string) => {
|
||||
setExpandedQuickGroups(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(groupId)) {
|
||||
newSet.delete(groupId);
|
||||
} else {
|
||||
newSet.add(groupId);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
setExpandedQuickGroup(prev => (prev === groupId ? null : groupId));
|
||||
setExpandedSubQuickGroup(null);
|
||||
};
|
||||
|
||||
const handleSubQuickGroupToggle = (groupId: string) => {
|
||||
setExpandedSubQuickGroup(prev => (prev === groupId ? null : groupId));
|
||||
};
|
||||
|
||||
const handleQuickGroupClick = (group: any) => {
|
||||
@ -298,7 +296,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
}
|
||||
}}
|
||||
>
|
||||
Общие
|
||||
Узлы
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
@ -333,7 +331,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
<>
|
||||
{(quickGroups as QuickGroup[]).map((group: QuickGroup) => {
|
||||
const hasChildren = group.children && group.children.length > 0;
|
||||
const isOpen = expandedQuickGroups.has(group.quickgroupid);
|
||||
const isOpen = expandedQuickGroup === group.quickgroupid;
|
||||
|
||||
if (!hasChildren) {
|
||||
return (
|
||||
@ -359,7 +357,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
|
||||
>
|
||||
<div
|
||||
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
|
||||
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open active" : ""}`}
|
||||
onClick={() => handleQuickGroupToggle(group.quickgroupid)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
@ -369,14 +367,14 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
<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);
|
||||
const isChildOpen = expandedSubQuickGroup === child.quickgroupid;
|
||||
|
||||
if (!hasSubChildren) {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
key={child.quickgroupid}
|
||||
className="dropdown-link-3 w-dropdown-link "
|
||||
className="dropdown-link-3 w-dropdown-link"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleQuickGroupClick(child);
|
||||
@ -395,8 +393,8 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
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)}
|
||||
className={`dropdown-toggle-card w-dropdown-toggle pl-0${isChildOpen ? " w--open active" : ""}`}
|
||||
onClick={() => handleSubQuickGroupToggle(child.quickgroupid)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<div className="w-icon-dropdown-toggle"></div>
|
||||
@ -407,7 +405,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
<a
|
||||
href="#"
|
||||
key={subChild.quickgroupid}
|
||||
className="dropdown-link-3 w-dropdown-link "
|
||||
className="dropdown-link-3 w-dropdown-link"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleQuickGroupClick(subChild);
|
||||
|
@ -65,6 +65,8 @@ const VinQuick: React.FC<VinQuickProps> = ({ quickGroup, catalogCode, vehicleId,
|
||||
alt={unit.name}
|
||||
className="image-26"
|
||||
onError={e => { (e.currentTarget as HTMLImageElement).src = '/images/image-44.jpg'; }}
|
||||
onClick={() => handleUnitClick(unit)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
) : (
|
||||
<img src="/images/image-44.jpg" alt="Нет изображения" className="image-26" />
|
||||
|
@ -404,7 +404,7 @@ const VehicleDetailsPage = () => {
|
||||
) : (
|
||||
<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>
|
||||
{/* <button onClick={() => setSelectedNode(null)} style={{ marginBottom: 16 }}>Назад</button> */}
|
||||
<KnotIn
|
||||
catalogCode={vehicleInfo.catalog}
|
||||
vehicleId={vehicleInfo.vehicleid}
|
||||
|
@ -474,4 +474,19 @@ input#VinSearchInput {
|
||||
|
||||
.dropdown-link-3 {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.div-block-131 {
|
||||
max-width: 230px;
|
||||
}
|
||||
|
||||
.dropdown-toggle-3.active, .dropdown-toggle-card.active {
|
||||
background-color: var(--background);
|
||||
|
||||
}
|
||||
|
||||
.dropdown-toggle-3.active {
|
||||
border-left: 2px solid var(--red);
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user