This commit is contained in:
2026-04-06 18:44:02 +05:00
commit d775fbb9ba
431 changed files with 31234 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
<div class="row g-2 justify-content-center categories-block">
@foreach ($categories as $category)
<div class="col-6 col-md-4 col-lg-3">
<a class="text-decoration-none" href="{{ $category->url }}">
<div class="card category-card"
style="background-image: url('{{ $category->image_url }}'); background-size: cover; background-position: center; height: 200px;">
<div class="card-body">
<h2>{{ $category->name }}</h2>
</div>
</div>
</a>
</div>
@endforeach
</div>

View File

@@ -0,0 +1,94 @@
<div id="{{ $field }}-step" data-step-{{ $field }}="{{ $step }}" class="row g-2 mb-2">
<div class="col-6">
<div class="input-group input-group-sm">
<span class="input-group-text bg-white border-end-0">от</span>
<input
data-min-{{ $field }}={{ $min }}
type="number"
class="form-control form-control-sm border-start-0"
id="{{ $field }}-min"
name="{{ $field }}-min"
value="{{ $currentMin ?? $min }}"
min="{{ $min }}"
max="{{ $max }}">
</div>
</div>
<div class="col-6">
<div class="input-group input-group-sm">
<span class="input-group-text bg-white border-end-0">до</span>
<input
data-max-{{ $field }}={{ $max }}
type="number"
class="form-control form-control-sm border-start-0"
id="{{ $field }}-max"
name="{{ $field }}-max"
value="{{ $currentMax ?? $max }}"
min="{{ $min }}"
max="{{ $max }}">
</div>
</div>
</div>
<div id="{{ $field }}-slider" class="ui-slider"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const sliderId = '{{ $field }}-slider';
const minId = '{{ $field }}-min';
const maxId = '{{ $field }}-max';
const stepId = '{{ $field }}-step';
const slider = document.getElementById(sliderId);
const fieldMin = document.getElementById(minId);
const fieldMax = document.getElementById(maxId);
const fieldStep = document.getElementById(stepId);
const dataAttrMin = 'data-min-{{ $field }}';
const dataAttrMax = 'data-max-{{ $field }}';
const dataAttrStep = 'data-step-{{ $field }}';
const minPossible = parseFloat(fieldMin.getAttribute(dataAttrMin));
const maxPossible = parseFloat(fieldMax.getAttribute(dataAttrMax));
const step = parseFloat(fieldStep.getAttribute(dataAttrStep));
const currentMin = parseFloat(fieldMin.value);
const currentMax = parseFloat(fieldMax.value);
noUiSlider.create(slider, {
start: [currentMin, currentMax],
connect: true,
range: {
'min': minPossible,
'max': maxPossible
},
step: step,
format: {
to: value => Math.round(value),
from: value => Math.round(value)
}
});
slider.noUiSlider.on('update', function(values, handle) {
if (handle === 0) {
fieldMin.value = values[0];
} else {
fieldMax.value = values[1];
}
});
fieldMin.addEventListener('change', function() {
let val = parseFloat(this.value);
if (val < minPossible) val = minPossible;
if (val > parseFloat(fieldMax.value)) val = parseFloat(fieldMax.value);
this.value = val;
slider.noUiSlider.set([val, null]);
});
fieldMax.addEventListener('change', function() {
let val = parseFloat(this.value);
if (val > maxPossible) val = maxPossible;
if (val < parseFloat(fieldMin.value)) val = parseFloat(fieldMin.value);
this.value = val;
slider.noUiSlider.set([null, val]);
});
});
</script>

View File

@@ -0,0 +1,191 @@
@extends('layouts.app')
@section('title', $category->name)
@php $style = 'category'; @endphp
@section('content')
<div class="container">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="{{ route('index') }}">Главная</a>
</li>
@foreach($category->breadcrumbs as $crumb)
@if($loop->last)
<li class="breadcrumb-item active">{{ $crumb['name'] }}</li>
@else
<li class="breadcrumb-item">
<a href="{{ $crumb['url'] }}">{{ $crumb['name'] }}</a>
</li>
@endif
@endforeach
</ol>
</nav>
<div class="d-flex flex-column gap-3 p-5 mb-4 tw-bg-dark-green tw-text-light-gray align-items-center">
<h1 class="fs-2">{{ $category->name }}</h1>
@if($category->description)
<small class="lead text-center d-none d-lg-block mt-3">{{ $category->description }}</small>
@endif
</div>
{{-- Подкатегории --}}
@if($children->count())
<div class="row mb-5 d-none d-lg-block">
<div class="col-12">
<h3 class="fs-4 mb-4 text-center">Подкатегории</h3>
@include('categories.partials.categories-block', ['categories' => $children])
</div>
</div>
@endif
{{-- Продукты --}}
<div class="row">
<div class="col-12 col-md-4 col-lg-3" class="h-100">
<div class="d-md-none col-12 mb-3">
<button class="btn-dark-green w-100 p-3" type="button" onclick="toggleFilters()">
<span id="btn-text">Показать фильтры</span>
</button>
</div>
<div id="mobile-filters" class="d-none d-md-block filters tw-bg-orange tw-text-light-gray p-3">
<button type="button" class="d-md-none close-btn" onclick="toggleFilters()">&times;</button>
{{-- ФОРМА ФИЛЬТРОВ --}}
<form method="GET" action="{{ route('category.show', $category->full_slug) }}" class="filters tw-bg-orange tw-text-light-gray p-3" id="filter-form">
{{-- Бренды --}}
@if($brands->count())
<div class="row align-items-start">
<div class="d-flex flex-column justify-content-between col-12 p-3 mb-3 tw-text-light-gray">
<p class="form-label fw-semibold mb-3">Бренды</p>
<div class="brands-list" style="max-height: 50%; overflow-y: auto;">
@foreach($brands as $brand)
<div class="form-check mb-2">
<input class="form-check-input"
type="checkbox"
name="brands[]"
value="{{ $brand->id }}"
id="brand_{{ $brand->id }}"
{{ in_array($brand->id, request('brands', [])) ? 'checked' : '' }}>
<label class="form-check-label" for="brand_{{ $brand->id }}">
{{ $brand->name }}
</label>
</div>
@endforeach
</div>
</div>
</div>
@endif
{{-- Цена --}}
<div class="row align-items-start">
<div class="d-flex flex-column justify-content-between col-12 p-3 mb-3 tw-text-light-gray">
<p class="form-label fw-semibold mb-2">Цена</p>
@include('categories.partials.ui-slider', [
'field' => 'price',
'max' => $maxPrice,
'min' => $minPrice,
'step' => 25,
'currentMin' => request('price-min', $minPrice),
'currentMax' => request('price-max', $maxPrice)
])
</div>
</div>
{{-- Кнопки --}}
<div class="d-flex flex-column justify-content-between col-12 p-3 mb-3 tw-text-light-gray">
<button type="submit" class="btn-dark-green" id="apply-filters-btn">Применить фильтры</button>
<a href="{{ route('category.show', $category->full_slug) }}" class="btn btn-danger mt-2 text-center d-block" id="reset-filters-btn">Сбросить</a>
</div>
</form>
</div>
</div>
{{-- БЛОК С КАРТОЧКАМИ --}}
<div class="col-12 col-md-8 col-lg-9" id="products-container">
@if($products->count())
<div class="row g-2 g-lg-4" id="products-grid">
@foreach($products as $product)
@include('products.partials.product-card', compact('product'))
@endforeach
</div>
{{ $products->links() }}
@else
<div class="alert alert-dark-green">
В этой категории пока нет товаров.
</div>
@endif
</div>
</div>
</div>
@endsection
@section('script')
<script>
const filters = document.getElementById('mobile-filters');
const btnText = document.getElementById('btn-text');
const body = document.body;
function toggleFilters() {
if (filters.classList.contains('d-none')) {
filters.classList.remove('d-none');
filters.classList.add('mobile-open');
body.classList.add('filters-open');
btnText.textContent = 'Скрыть фильтры';
} else {
filters.classList.add('d-none');
filters.classList.remove('mobile-open');
body.classList.remove('filters-open');
btnText.textContent = 'Показать фильтры';
}
}
function scrollToProducts() {
const productsContainer = document.getElementById('products-container');
if (productsContainer) {
const offset = 80;
const elementPosition = productsContainer.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - offset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
}
document.addEventListener('DOMContentLoaded', function() {
const filterForm = document.getElementById('filter-form');
const resetBtn = document.getElementById('reset-filters-btn');
if (filterForm) {
filterForm.addEventListener('submit', function(e) {
localStorage.setItem('scrollToProducts', 'true');
});
}
if (resetBtn) {
resetBtn.addEventListener('click', function(e) {
localStorage.setItem('scrollToProducts', 'true');
});
}
if (localStorage.getItem('scrollToProducts') === 'true') {
setTimeout(() => {
scrollToProducts();
localStorage.removeItem('scrollToProducts');
}, 300);
}
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.toString().length > 0) {
setTimeout(() => {
scrollToProducts();
}, 300);
}
});
</script>
@endsection