first commit

This commit is contained in:
Bivekich
2025-06-26 06:59:59 +03:00
commit d44874775c
450 changed files with 76635 additions and 0 deletions

View File

@ -0,0 +1,163 @@
import React, { useState, useMemo, useRef, useEffect } from 'react';
import { useQuery } from '@apollo/client';
import { Combobox } from '@headlessui/react';
import { GET_LAXIMO_BRANDS, GET_LAXIMO_CATALOG_INFO } from '@/lib/graphql';
import { LaximoBrand, LaximoVehicleSearchResult } from '@/types/laximo';
import WizardSearchForm from './WizardSearchForm';
import VehicleSearchResults from './VehicleSearchResults';
import { useRouter } from 'next/router';
const BrandWizardSearchSection: React.FC = () => {
const router = useRouter();
const { data: brandsData, loading: brandsLoading, error: brandsError } = useQuery<{ laximoBrands: LaximoBrand[] }>(GET_LAXIMO_BRANDS, { errorPolicy: 'all' });
const [selectedBrand, setSelectedBrand] = useState<LaximoBrand | null>(null);
const [vehicles, setVehicles] = useState<LaximoVehicleSearchResult[] | null>(null);
const [brandQuery, setBrandQuery] = useState('');
const buttonRef = useRef<HTMLButtonElement>(null);
// Получение информации о каталоге через useQuery
const {
data: catalogData,
loading: catalogLoading,
error: catalogError
} = useQuery(
GET_LAXIMO_CATALOG_INFO,
{
variables: { catalogCode: selectedBrand?.code },
skip: !selectedBrand,
errorPolicy: 'all',
}
);
// Мемоизация брендов для селекта
const brands = useMemo(() => {
if (brandsData?.laximoBrands?.length) {
return [...brandsData.laximoBrands].sort((a, b) => a.name.localeCompare(b.name));
}
return [];
}, [brandsData]);
// Фильтрация брендов по поисковому запросу
const filteredBrands = useMemo(() => {
if (!brandQuery) return brands;
return brands.filter(b => b.name.toLowerCase().includes(brandQuery.toLowerCase()));
}, [brands, brandQuery]);
// Автоматически выбираем бренд из query, если есть selected
useEffect(() => {
if (!brandsData?.laximoBrands) return;
const selected = router.query.selected;
if (selected && !selectedBrand) {
const found = brandsData.laximoBrands.find(b => b.name.toLowerCase() === String(selected).toLowerCase() || b.code.toLowerCase() === String(selected).toLowerCase());
if (found) setSelectedBrand(found);
}
}, [brandsData, router.query.selected, selectedBrand]);
// Обработчик выбора бренда
const handleBrandChange = (brand: LaximoBrand | null) => {
setSelectedBrand(brand);
setVehicles(null);
};
// Обработчик найденных авто
const handleVehicleFound = (vehicles: LaximoVehicleSearchResult[]) => {
setVehicles(vehicles);
};
// Каталожная информация
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">
{/* <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">
<div className="w-full max-w-[320px] min-w-[320px]">
<div className="flex items-center justify-between mb-[12px]" >
<div className="flex items-center space-x-3">
<h4 className="text-lg font-medium text-gray-900">
Марка автомобиля
</h4>
</div>
</div>
<Combobox value={selectedBrand} onChange={handleBrandChange} nullable>
<div className="relative">
<Combobox.Input
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"
/>
<Combobox.Button 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>
</div>
</Combobox>
</div>
</div>
{/* Каталог и wizard */}
{catalogLoading && selectedBrand && (
<div className="flex items-center justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-red-600"></div>
<span className="ml-3 text-gray-600">Загружаем каталог...</span>
</div>
)}
{catalogError && selectedBrand && (
<div className="text-red-600 text-center py-4">Ошибка загрузки каталога</div>
)}
{catalogInfo && catalogInfo.supportparameteridentification2 && (
<>
<div className="mt-6">
<WizardSearchForm
catalogCode={catalogInfo.code}
onVehicleFound={handleVehicleFound}
/>
</div>
{vehicles && (
<div className="mt-8">
<VehicleSearchResults results={vehicles} catalogInfo={catalogInfo} />
</div>
)}
</>
)}
{catalogInfo && !catalogInfo.supportparameteridentification2 && (
<div className="text-yellow-700 bg-yellow-50 border border-yellow-200 rounded-lg p-4 mt-6 text-center">
Для выбранного бренда подбор по параметрам недоступен.
</div>
)}
</section>
);
};
export default BrandWizardSearchSection;