This commit is contained in:
266
resources/views/shop/category.blade.php
Normal file
266
resources/views/shop/category.blade.php
Normal file
@@ -0,0 +1,266 @@
|
||||
@extends('layouts.shop')
|
||||
|
||||
@php
|
||||
$hasSeoFilters = request()->filled('q')
|
||||
|| request()->filled('sort')
|
||||
|| request()->filled('page')
|
||||
|| request()->filled('price_from')
|
||||
|| request()->filled('price_to')
|
||||
|| collect((array) request('filters', []))
|
||||
->contains(fn ($value) => is_scalar($value) && trim((string) $value) !== '')
|
||||
|| collect(request()->query())
|
||||
->keys()
|
||||
->contains(fn ($key) => is_string($key) && (str_ends_with($key, '_from') || str_ends_with($key, '_to')));
|
||||
|
||||
$categoryItemList = collect($products->items())
|
||||
->values()
|
||||
->map(fn ($product, $index) => [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $index + 1,
|
||||
'url' => route('products.show', $product),
|
||||
'name' => $product->name,
|
||||
])
|
||||
->all();
|
||||
$categorySchema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'CollectionPage',
|
||||
'name' => $category->name,
|
||||
'url' => route('catalog.category', $category),
|
||||
'description' => $category->description ?: 'Категория товаров ' . $category->name,
|
||||
'mainEntity' => [
|
||||
'@type' => 'ItemList',
|
||||
'numberOfItems' => $products->total(),
|
||||
'itemListElement' => $categoryItemList,
|
||||
],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@section('meta_title', $category->name)
|
||||
@section('meta_description', ($category->description ?: 'Товары категории ' . $category->name . '.') . ' Фильтры и сортировка для быстрого подбора.')
|
||||
@section('meta_keywords', $category->name . ', комплектующие, купить, фильтры товаров')
|
||||
@section('meta_canonical', route('catalog.category', $category))
|
||||
@section('meta_robots', $hasSeoFilters ? 'noindex,follow' : 'index,follow')
|
||||
|
||||
@push('structured_data')
|
||||
<script type="application/ld+json">
|
||||
@json($categorySchema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
@include('partials.breadcrumbs', [
|
||||
'items' => [
|
||||
['label' => 'Главная', 'url' => route('home')],
|
||||
['label' => 'Каталог', 'url' => route('catalog.index')],
|
||||
['label' => $category->name, 'url' => null],
|
||||
],
|
||||
])
|
||||
|
||||
<section class="pc-section pc-category-page">
|
||||
<div class="pc-section-title">
|
||||
<h2>{{ $category->name }}</h2>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$activeFilters = collect((array) ($appliedFilters ?? []))
|
||||
->filter(fn ($value) => is_scalar($value) && trim((string) $value) !== '')
|
||||
->values();
|
||||
|
||||
if (request()->filled('price_from') || request()->filled('price_to')) {
|
||||
$priceFromLabel = trim((string) request('price_from', ''));
|
||||
$priceToLabel = trim((string) request('price_to', ''));
|
||||
$activeFilters->push("Цена: {$priceFromLabel} - {$priceToLabel}");
|
||||
}
|
||||
|
||||
foreach ((array) ($filters ?? []) as $filter) {
|
||||
if ((string) ($filter['filter'] ?? 'select') !== 'range') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rangeKey = (string) ($filter['key'] ?? '');
|
||||
if ($rangeKey === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fromParam = $rangeKey . '_from';
|
||||
$toParam = $rangeKey . '_to';
|
||||
if (!request()->filled($fromParam) && !request()->filled($toParam)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fromLabel = trim((string) request($fromParam, ''));
|
||||
$toLabel = trim((string) request($toParam, ''));
|
||||
$activeFilters->push(($filter['label'] ?? $rangeKey) . ": {$fromLabel} - {$toLabel}");
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div class="pc-category-toolbar">
|
||||
<p class="pc-muted">Найдено товаров: <strong>{{ $products->total() }}</strong></p>
|
||||
<div class="pc-category-toolbar-controls">
|
||||
<form class="pc-sort-form" method="get">
|
||||
@foreach ((array) ($appliedFilters ?? []) as $key => $value)
|
||||
@if (is_scalar($value) && trim((string) $value) !== '')
|
||||
<input type="hidden" name="filters[{{ $key }}]" value="{{ $value }}">
|
||||
@endif
|
||||
@endforeach
|
||||
@if (request()->filled('price_from'))
|
||||
<input type="hidden" name="price_from" value="{{ request('price_from') }}">
|
||||
@endif
|
||||
@if (request()->filled('price_to'))
|
||||
<input type="hidden" name="price_to" value="{{ request('price_to') }}">
|
||||
@endif
|
||||
@foreach ((array) ($filters ?? []) as $filter)
|
||||
@php
|
||||
$rangeKey = (string) ($filter['key'] ?? '');
|
||||
@endphp
|
||||
@continue($rangeKey === '' || (string) ($filter['filter'] ?? 'select') !== 'range')
|
||||
@if (request()->filled($rangeKey . '_from'))
|
||||
<input type="hidden" name="{{ $rangeKey }}_from" value="{{ request($rangeKey . '_from') }}">
|
||||
@endif
|
||||
@if (request()->filled($rangeKey . '_to'))
|
||||
<input type="hidden" name="{{ $rangeKey }}_to" value="{{ request($rangeKey . '_to') }}">
|
||||
@endif
|
||||
@endforeach
|
||||
@if (request()->filled('q'))
|
||||
<input type="hidden" name="q" value="{{ request('q') }}">
|
||||
@endif
|
||||
<label for="sort">Сортировка:</label>
|
||||
<select id="sort" name="sort" onchange="this.form.submit()">
|
||||
<option value="newest" @selected($sort === 'newest')>Сначала новые</option>
|
||||
<option value="price_asc" @selected($sort === 'price_asc')>Сначала дешевле</option>
|
||||
<option value="price_desc" @selected($sort === 'price_desc')>Сначала дороже</option>
|
||||
<option value="name_asc" @selected($sort === 'name_asc')>По названию</option>
|
||||
</select>
|
||||
</form>
|
||||
<button
|
||||
class="pc-filter-inline-toggle"
|
||||
type="button"
|
||||
data-filter-toggle
|
||||
aria-controls="pc-category-filters"
|
||||
aria-expanded="{{ $activeFilters->isNotEmpty() ? 'true' : 'false' }}"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M4 6h16v2H4V6zm3 5h10v2H7v-2zm3 5h4v2h-4v-2z"></path>
|
||||
</svg>
|
||||
<span>Фильтр</span>
|
||||
@if ($activeFilters->isNotEmpty())
|
||||
<span class="pc-filter-toggle-count">{{ $activeFilters->count() }}</span>
|
||||
@endif
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@if ($activeFilters->isNotEmpty())
|
||||
<div class="pc-filter-tags">
|
||||
@foreach ($activeFilters as $key => $value)
|
||||
<span class="pc-filter-chip">{{ $value }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="pc-catalog-layout">
|
||||
<aside class="pc-filters">
|
||||
<div id="pc-category-filters" class="pc-filter-details {{ $activeFilters->isNotEmpty() ? 'is-open' : '' }}">
|
||||
<form method="get">
|
||||
<div class="pc-filter-title">Фильтры</div>
|
||||
@if ($sort !== 'newest')
|
||||
<input type="hidden" name="sort" value="{{ $sort }}">
|
||||
@endif
|
||||
@if (request()->filled('q'))
|
||||
<input type="hidden" name="q" value="{{ request('q') }}">
|
||||
@endif
|
||||
<label class="pc-filter-block">
|
||||
<span>Цена</span>
|
||||
<div class="pc-range-fields">
|
||||
<input
|
||||
type="number"
|
||||
name="price_from"
|
||||
step="1"
|
||||
value="{{ $priceFilter['from'] ?? '' }}"
|
||||
@if (!empty($priceFilter['min'])) min="{{ $priceFilter['min'] }}" @endif
|
||||
@if (!empty($priceFilter['max'])) max="{{ $priceFilter['max'] }}" @endif
|
||||
placeholder="От"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
name="price_to"
|
||||
step="1"
|
||||
value="{{ $priceFilter['to'] ?? '' }}"
|
||||
@if (!empty($priceFilter['min'])) min="{{ $priceFilter['min'] }}" @endif
|
||||
@if (!empty($priceFilter['max'])) max="{{ $priceFilter['max'] }}" @endif
|
||||
placeholder="До"
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
@forelse ($filters as $filter)
|
||||
@php
|
||||
$filterKey = (string) ($filter['key'] ?? '');
|
||||
$isRangeFilter = (string) ($filter['filter'] ?? 'select') === 'range';
|
||||
$rangeData = (array) ($rangeFilters[$filterKey] ?? []);
|
||||
@endphp
|
||||
@continue($filterKey === '')
|
||||
@if ($isRangeFilter)
|
||||
<label class="pc-filter-block">
|
||||
<span>{{ $filter['label'] }}</span>
|
||||
<div class="pc-range-fields">
|
||||
<input
|
||||
type="number"
|
||||
name="{{ $filterKey }}_from"
|
||||
step="1"
|
||||
value="{{ $rangeData['from'] ?? '' }}"
|
||||
@if (!empty($rangeData['min'])) min="{{ $rangeData['min'] }}" @endif
|
||||
@if (!empty($rangeData['max'])) max="{{ $rangeData['max'] }}" @endif
|
||||
placeholder="От"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
name="{{ $filterKey }}_to"
|
||||
step="1"
|
||||
value="{{ $rangeData['to'] ?? '' }}"
|
||||
@if (!empty($rangeData['min'])) min="{{ $rangeData['min'] }}" @endif
|
||||
@if (!empty($rangeData['max'])) max="{{ $rangeData['max'] }}" @endif
|
||||
placeholder="До"
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
@else
|
||||
<label class="pc-filter-block">
|
||||
<span>{{ $filter['label'] }}</span>
|
||||
<select name="filters[{{ $filterKey }}]">
|
||||
<option value="">Все</option>
|
||||
@foreach ($filterOptions[$filterKey] ?? [] as $option)
|
||||
@php
|
||||
$optionValue = trim((string) $option);
|
||||
@endphp
|
||||
@continue($optionValue === '')
|
||||
<option value="{{ $optionValue }}" @selected(($appliedFilters[$filterKey] ?? '') === $optionValue)>
|
||||
{{ $optionValue }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</label>
|
||||
@endif
|
||||
@empty
|
||||
<p class="pc-muted">Для этой категории фильтры пока не заданы.</p>
|
||||
@endforelse
|
||||
<div class="pc-filter-actions">
|
||||
<button type="submit" class="pc-btn primary">Показать</button>
|
||||
<a class="pc-btn ghost" href="{{ route('catalog.category', $category) }}">Сбросить</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="pc-products-grid">
|
||||
@forelse ($products as $product)
|
||||
@include('partials.product-card', ['product' => $product])
|
||||
@empty
|
||||
<div class="pc-card">Пока нет товаров в этой категории.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pc-pagination">
|
||||
{{ $products->links('partials.pagination') }}
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user