Другой роутинг
Some checks failed
CI / main (push) Has been cancelled

This commit is contained in:
Igor Rybakov
2026-03-06 23:24:16 +02:00
parent 6228624805
commit f919ea66f6
19 changed files with 500 additions and 160 deletions

View File

@@ -1,39 +1,40 @@
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import { Icon } from '@/components/Icon/Icon';
import { categories } from '@/data/categories';
import { brands } from '@/data/brands';
import { products } from '@/data/products';
import { useCatalogFilters } from '@/hooks/useCatalogFilters';
import { getSubcategories } from '@/lib/getSubcategories';
import styles from './CatalogSidebar.module.scss';
interface CatalogSidebarProps {
activeCategory?: string;
basePath?: string;
hiddenFilters?: string[];
}
export function CatalogSidebar({ activeCategory }: CatalogSidebarProps) {
const router = useRouter();
const searchParams = useSearchParams();
const activeBrand = searchParams.get('brand');
export function CatalogSidebar({
basePath = '/catalog',
hiddenFilters = [],
}: CatalogSidebarProps) {
const { filters, setFilter } = useCatalogFilters(basePath);
const handleCategoryClick = (slug: string | null) => {
if (slug) {
router.push(`/catalog/${slug}`);
} else {
router.push('/catalog');
}
const toggleFilter = (
key: 'category' | 'brand' | 'subcategory' | 'sort',
value: string
) => {
setFilter(key, filters[key] === value ? undefined : value);
};
const handleBrandClick = (brandId: number) => {
router.push(`/brand/${brandId}`);
};
const filteredBrands = activeCategory
const filteredBrands = filters.category
? brands.filter((b) => {
const cat = categories.find((c) => c.slug === activeCategory);
const cat = categories.find((c) => c.slug === filters.category);
return cat ? b.category === cat.name : true;
})
: brands;
const subcategories = getSubcategories(products, filters.category);
return (
<aside className={styles.sidebar}>
<div className={styles.inner}>
@@ -42,35 +43,84 @@ export function CatalogSidebar({ activeCategory }: CatalogSidebarProps) {
Фильтры
</div>
{/* Category */}
<div className={styles.filterGroup}>
<div className={styles.filterLabel}>Направление</div>
<button
className={`${styles.filterItem} ${!activeCategory ? styles.filterItemActive : ''}`}
onClick={() => handleCategoryClick(null)}
className={`${styles.filterItem} ${!filters.category ? styles.filterItemActive : ''}`}
onClick={() => setFilter('category', undefined)}
>
Все
</button>
{categories.map((c) => (
<button
key={c.slug}
className={`${styles.filterItem} ${activeCategory === c.slug ? styles.filterItemActive : ''}`}
onClick={() => handleCategoryClick(c.slug)}
className={`${styles.filterItem} ${filters.category === c.slug ? styles.filterItemActive : ''}`}
onClick={() => toggleFilter('category', c.slug)}
>
{c.name}
</button>
))}
</div>
{/* Brand */}
{!hiddenFilters.includes('brand') && (
<div className={styles.filterGroup}>
<div className={styles.filterLabel}>Бренд</div>
{filteredBrands.slice(0, 6).map((b) => (
<button
key={b.id}
className={`${styles.filterItem} ${filters.brand === b.name ? styles.filterItemActive : ''}`}
onClick={() => toggleFilter('brand', b.name)}
>
<span>{b.name}</span>
<span className={styles.brandCountry}>{b.country}</span>
</button>
))}
</div>
)}
{/* Subcategory */}
{subcategories.length > 0 && (
<div className={styles.filterGroup}>
<div className={styles.filterLabel}>Подкатегория</div>
{subcategories.map((sub) => (
<button
key={sub}
className={`${styles.filterItem} ${filters.subcategory === sub ? styles.filterItemActive : ''}`}
onClick={() => toggleFilter('subcategory', sub)}
>
{sub}
</button>
))}
</div>
)}
{/* In Stock */}
<div className={styles.filterGroup}>
<div className={styles.filterLabel}>Бренд</div>
{filteredBrands.slice(0, 6).map((b) => (
<div className={styles.filterLabel}>Наличие</div>
<button
className={`${styles.filterItem} ${filters.inStock ? styles.filterItemActive : ''}`}
onClick={() => setFilter('inStock', filters.inStock ? undefined : true)}
>
В наличии
</button>
</div>
{/* Sort */}
<div className={styles.filterGroup}>
<div className={styles.filterLabel}>Сортировка</div>
{[
{ value: 'name', label: 'По названию' },
{ value: 'brand', label: 'По бренду' },
{ value: 'category', label: 'По категории' },
].map((opt) => (
<button
key={b.id}
className={`${styles.filterItem} ${activeBrand === String(b.id) ? styles.filterItemActive : ''}`}
onClick={() => handleBrandClick(b.id)}
key={opt.value}
className={`${styles.filterItem} ${filters.sort === opt.value ? styles.filterItemActive : ''}`}
onClick={() => toggleFilter('sort', opt.value)}
>
<span>{b.name}</span>
<span className={styles.brandCountry}>{b.country}</span>
{opt.label}
</button>
))}
</div>

View File

@@ -0,0 +1,43 @@
.layout {
max-width: var(--max-width);
margin: 0 auto;
padding: var(--space-lg);
display: flex;
gap: var(--space-lg);
}
.main {
flex: 1;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-md);
}
.count {
font-size: 12px;
color: var(--color-slate-400);
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-md);
@media (max-width: 1024px) {
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 640px) {
grid-template-columns: 1fr;
}
}
.empty {
text-align: center;
color: var(--color-text-muted);
padding: var(--space-2xl) 0;
}

View File

@@ -0,0 +1,49 @@
import { Suspense } from 'react';
import { Product } from '@/types';
import { Breadcrumb } from '@/components/Breadcrumb/Breadcrumb';
import { CatalogSidebar } from '@/components/CatalogSidebar/CatalogSidebar';
import { ProductCard } from '@/components/ProductCard/ProductCard';
import styles from './CatalogView.module.scss';
interface BreadcrumbItem {
label: string;
href?: string;
}
interface CatalogViewProps {
products: Product[];
breadcrumbItems: BreadcrumbItem[];
basePath?: string;
hiddenFilters?: string[];
}
export function CatalogView({
products,
breadcrumbItems,
basePath = '/catalog',
hiddenFilters,
}: CatalogViewProps) {
return (
<div className={styles.layout}>
<Suspense fallback={null}>
<CatalogSidebar basePath={basePath} hiddenFilters={hiddenFilters} />
</Suspense>
<div className={styles.main}>
<div className={styles.header}>
<Breadcrumb items={breadcrumbItems} />
<span className={styles.count}>{products.length} позиций</span>
</div>
<div className={styles.grid}>
{products.map((p) => (
<ProductCard key={p.id} product={p} />
))}
</div>
{products.length === 0 && (
<p className={styles.empty}>
По выбранным фильтрам ничего не найдено.
</p>
)}
</div>
</div>
);
}

View File

@@ -13,7 +13,7 @@ export function Directions() {
{categories.map((d) => (
<Link
key={d.slug}
href={`/catalog/${d.slug}`}
href={`/catalog?category=${d.slug}`}
className={styles.card}
>
<div

View File

@@ -117,7 +117,7 @@ export function Header() {
)}
</Link>
<Link href="/rfq" className={styles.rfqButton}>
Запрос цены (RFQ)
Запрос цены
</Link>
<button
className={styles.menuToggle}

View File

@@ -38,7 +38,7 @@ export function Hero() {
<Icon name="chevronRight" size={16} color="#fff" />
</Link>
<Link href="/rfq" className={styles.ctaSecondary}>
Отправить запрос (RFQ)
Отправить запрос
</Link>
</div>
</div>

View File

@@ -48,7 +48,7 @@ export function ProductDetail({ product }: { product: Product }) {
В корзину
</Button>
<Link href="/rfq">
<Button variant="secondary">Запросить цену (RFQ)</Button>
<Button variant="secondary">Запросить цену</Button>
</Link>
</div>
</div>