load product ajax
This commit is contained in:
parent
8548016200
commit
80f6dc8612
|
|
@ -12,27 +12,66 @@ use Illuminate\Support\Facades\Cache;
|
|||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
public function genders(Request $request)
|
||||
{
|
||||
$genderRepository = new GenderRepository;
|
||||
$genders = $genderRepository->getList([]);
|
||||
|
||||
// Render gender links HTML
|
||||
$genderHtml = '';
|
||||
$currentGenderId = $request->input('current_gender');
|
||||
|
||||
foreach ($genders as $gender) {
|
||||
$isActive = $currentGenderId == $gender->id;
|
||||
$genderHtml .= '<li class="nav-item mb-1">';
|
||||
$genderHtml .= '<a class="nav-link d-block fw-normal p-0 ' . ($isActive ? 'active text-primary' : '') . '" ';
|
||||
$genderHtml .= 'href="#" data-gender-id="' . $gender->id . '">';
|
||||
$genderHtml .= $gender->name;
|
||||
$genderHtml .= '</a></li>';
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'genders' => $genderHtml
|
||||
]);
|
||||
}
|
||||
|
||||
public function categories(Request $request)
|
||||
{
|
||||
$categoryRepository = new CategoryRepository;
|
||||
$categories = $categoryRepository->getList([]);
|
||||
|
||||
// Render category links HTML
|
||||
$categoryHtml = '';
|
||||
$currentCategoryId = $request->input('current_category');
|
||||
|
||||
foreach ($categories as $category) {
|
||||
$isActive = $currentCategoryId == $category->id;
|
||||
$categoryHtml .= '<li class="nav-item mb-1">';
|
||||
$categoryHtml .= '<a class="nav-link d-block fw-normal p-0 ' . ($isActive ? 'active text-primary' : '') . '" ';
|
||||
$categoryHtml .= 'href="#" data-category-id="' . $category->id . '">';
|
||||
$categoryHtml .= $category->name;
|
||||
$categoryHtml .= '</a></li>';
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'categories' => $categoryHtml
|
||||
]);
|
||||
}
|
||||
|
||||
public function ajax(Request $request)
|
||||
{
|
||||
$limit = 20;
|
||||
$page = $request->page ?? 1;
|
||||
|
||||
$search = $request->search;
|
||||
|
||||
$filter = $request->filter ?? [];
|
||||
$sortBy = $request->sort_by ?? 'relevance';
|
||||
|
||||
$price_range_start = $request->price_range_start ?? null;
|
||||
$price_range_end = $request->price_range_end ?? null;
|
||||
|
||||
$genderRepository = new GenderRepository;
|
||||
$categoryRepository = new CategoryRepository;
|
||||
|
||||
$genders = $genderRepository->getList([]);
|
||||
$categories = $categoryRepository->getList([]);
|
||||
|
||||
|
||||
|
||||
$user = auth()->user();
|
||||
$userId = $user ? $user->id : 0;
|
||||
[$location_id, $is_consignment] = Cache::remember('employee_user_'.$userId, 60 * 60 * 24, function () use ($user) {
|
||||
|
|
@ -63,7 +102,17 @@ class ProductController extends Controller
|
|||
'price_range_end' => $price_range_end,
|
||||
]);
|
||||
|
||||
// Render product cards HTML
|
||||
$productHtml = '';
|
||||
foreach ($products as $product) {
|
||||
$productHtml .= '<div class="col-6 col-md-4 mb-2 mb-sm-3 mb-md-0">';
|
||||
$productHtml .= view('components.home.product-card', ['product' => $product])->render();
|
||||
$productHtml .= '</div>';
|
||||
}
|
||||
|
||||
|
||||
// filter
|
||||
$filter = $request->filter ?? [];
|
||||
if (isset($filter['category']) && $filter['category']){
|
||||
$category = StoreCategory::find($filter['category']);
|
||||
|
||||
|
|
@ -87,16 +136,32 @@ class ProductController extends Controller
|
|||
|
||||
$filters = $filter;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'filters' => $filters,
|
||||
'products' => $productHtml,
|
||||
'count' => count($products),
|
||||
'has_more' => count($products) >= $limit
|
||||
]);
|
||||
}
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
||||
|
||||
$productRepository = new ProductRepository;
|
||||
$products = [];
|
||||
|
||||
|
||||
|
||||
$filters = [];
|
||||
|
||||
$min_max_price = $productRepository->getMinMaxPrice();
|
||||
|
||||
|
||||
return view('shop.catalog-fashion', [
|
||||
'filters' => $filters,
|
||||
'genders' => $genders,
|
||||
'categories' => $categories,
|
||||
|
||||
|
||||
'products' => $products,
|
||||
'page' => $page,
|
||||
'min_max_price' => $min_max_price,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,28 +33,8 @@
|
|||
data-bs-target="#filterSidebar" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas-body flex-column pt-2 py-lg-0">
|
||||
@if (count($filters) > 0)
|
||||
<!-- Selected filters + Sorting -->
|
||||
<div class="pb-4 mb-2 mb-xl-3">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<h4 class="h6 mb-0">Filter</h4>
|
||||
<a type="button" href="{{ route('product.index') }}"
|
||||
class="btn btn-sm btn-secondary bg-transparent border-0 text-decoration-underline p-0 ms-2">
|
||||
Clear all
|
||||
</a>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
@foreach ($filters as $key => $filter)
|
||||
<a href="{{ route('product.index',[http_build_query(request()->except('filter.'.$key))]) }}" type="button" class="btn btn-sm btn-secondary">
|
||||
<i class="ci-close fs-sm ms-n1 me-1"></i>
|
||||
{{ $filter }}
|
||||
</a>
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="offcanvas-body flex-column pt-2 py-lg-0 filter-sidebar">
|
||||
|
||||
|
||||
<div class="accordion">
|
||||
|
||||
|
|
@ -71,14 +51,8 @@
|
|||
aria-labelledby="headingGenders">
|
||||
<div class="accordion-body p-0 pb-4 mb-1 mb-xl-2">
|
||||
<div style="height: 220px" data-simplebar data-simplebar-auto-hide="false">
|
||||
<ul class="nav flex-column gap-2 pe-3">
|
||||
@foreach ($genders as $gender)
|
||||
<li class="nav-item mb-1">
|
||||
<a class="nav-link d-block fw-normal p-0" href="{{ route('product.index', array_merge(request()->except('filter[gender]'), ['filter[gender]' => $gender->id])) }}">
|
||||
{{ $gender->name }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
<ul class="nav flex-column gap-2 pe-3" id="genders-list">
|
||||
<!-- Genders will be loaded here via AJAX -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -97,14 +71,8 @@
|
|||
aria-labelledby="headingCategories">
|
||||
<div class="accordion-body p-0 pb-4 mb-1 mb-xl-2">
|
||||
<div style="height: 220px" data-simplebar data-simplebar-auto-hide="false">
|
||||
<ul class="nav flex-column gap-2 pe-3">
|
||||
@foreach ($categories as $category)
|
||||
<li class="nav-item mb-1">
|
||||
<a class="nav-link d-block fw-normal p-0" href="{{ route('product.index', array_merge(request()->except('filter[category]'), ['filter[category]' => $category->id])) }}">
|
||||
{{ $category->name }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
<ul class="nav flex-column gap-2 pe-3" id="categories-list">
|
||||
<!-- Categories will be loaded here via AJAX -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -471,7 +439,7 @@
|
|||
|
||||
<!-- Sorting -->
|
||||
<div class="d-sm-flex align-items-center justify-content-between mt-n2 mb-3 mb-sm-4">
|
||||
<div class="fs-sm text-body-emphasis text-nowrap">Found <span class="fw-semibold">{{ count($products) }}</span>
|
||||
<div class="fs-sm text-body-emphasis text-nowrap">Found <span class="fw-semibold" id="product-count">Loading...</span>
|
||||
items
|
||||
</div>
|
||||
<div class="d-flex align-items-center text-nowrap">
|
||||
|
|
@ -495,18 +463,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row gy-4 gy-md-5 pb-4 pb-md-5">
|
||||
|
||||
@foreach ($products as $key => $value)
|
||||
<!-- Item -->
|
||||
<div class="col-6 col-md-4 mb-2 mb-sm-3 mb-md-0">
|
||||
<x-home.product-card :product="$value" />
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
|
||||
|
||||
<!-- Banner -->
|
||||
<div class="row gy-4 gy-md-5 pb-4 pb-md-5" id="products-container">
|
||||
<!-- Products will be loaded here via AJAX -->
|
||||
</div>
|
||||
{{-- <div class="col-12 col-md-8 mb-2 mb-sm-3 mb-md-0">
|
||||
<div
|
||||
class="position-relative bg-body-tertiary text-center rounded-4 p-4 p-sm-5 py-md-4 py-xl-5">
|
||||
|
|
@ -551,7 +510,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Show more button -->
|
||||
<a href="{{ route('product.index', array_merge(request()->except('page'), ['page' => $page + 1])) }}" type="button" class="btn btn-lg btn-outline-secondary w-100">
|
||||
<a href="{{ route('product.index', array_merge(request()->except('page'), ['page' => 1])) }}" type="button" class="btn btn-lg btn-outline-secondary w-100">
|
||||
Show more
|
||||
<i class="ci-chevron-down fs-xl ms-2 me-n1"></i>
|
||||
</a>
|
||||
|
|
@ -635,6 +594,264 @@
|
|||
<i class="ci-filter fs-base me-2"></i>
|
||||
Filters
|
||||
</button>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadGenders();
|
||||
loadCategories();
|
||||
loadProducts();
|
||||
|
||||
// Handle sort change
|
||||
const sortSelect = document.querySelector('select[onchange*="sort_by"]');
|
||||
if (sortSelect) {
|
||||
sortSelect.removeAttribute('onchange');
|
||||
sortSelect.addEventListener('change', function() {
|
||||
loadProducts({ sort_by: this.value });
|
||||
});
|
||||
}
|
||||
|
||||
// Handle price range changes
|
||||
const priceInputs = document.querySelectorAll('[data-range-slider-min], [data-range-slider-max]');
|
||||
priceInputs.forEach(input => {
|
||||
input.addEventListener('change', function() {
|
||||
const minVal = document.querySelector('[data-range-slider-min]').value;
|
||||
const maxVal = document.querySelector('[data-range-slider-max]').value;
|
||||
loadProducts({
|
||||
price_range_start: minVal || null,
|
||||
price_range_end: maxVal || null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Attach initial filter event listeners
|
||||
attachFilterEventListeners();
|
||||
});
|
||||
|
||||
function removeFilter(filterKey) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
urlParams.delete(`filter[${filterKey}]`);
|
||||
|
||||
// Convert URLSearchParams to object for loadProducts
|
||||
const params = {};
|
||||
for (const [key, value] of urlParams.entries()) {
|
||||
params[key] = value;
|
||||
}
|
||||
|
||||
loadProducts(params);
|
||||
}
|
||||
|
||||
function clearAllFilters() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Remove all filter parameters
|
||||
for (const [key] of urlParams.entries()) {
|
||||
if (key.startsWith('filter[')) {
|
||||
urlParams.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep non-filter parameters
|
||||
const params = {};
|
||||
for (const [key, value] of urlParams.entries()) {
|
||||
if (!key.startsWith('filter[')) {
|
||||
params[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
loadProducts(params);
|
||||
}
|
||||
|
||||
function loadGenders() {
|
||||
const currentGenderId = new URLSearchParams(window.location.search).get('filter[gender]');
|
||||
|
||||
fetch(`{{ route('product.ajax.genders') }}?current_gender=${currentGenderId || ''}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('genders-list').innerHTML = data.genders;
|
||||
|
||||
// Attach event listeners to newly loaded gender links
|
||||
const genderLinks = document.querySelectorAll('#genders-list a[data-gender-id]');
|
||||
genderLinks.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
// Remove active class from all gender links
|
||||
genderLinks.forEach(g => g.classList.remove('active', 'text-primary'));
|
||||
// Add active class to clicked link
|
||||
this.classList.add('active', 'text-primary');
|
||||
|
||||
const genderId = this.getAttribute('data-gender-id');
|
||||
loadProducts({ 'filter[gender]': genderId });
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading genders:', error);
|
||||
document.getElementById('genders-list').innerHTML = '<li class="nav-item mb-1"><span class="text-danger">Error loading genders</span></li>';
|
||||
});
|
||||
}
|
||||
|
||||
function loadCategories() {
|
||||
const currentCategoryId = new URLSearchParams(window.location.search).get('filter[category]');
|
||||
|
||||
fetch(`{{ route('product.ajax.categories') }}?current_category=${currentCategoryId || ''}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('categories-list').innerHTML = data.categories;
|
||||
|
||||
// Attach event listeners to newly loaded category links
|
||||
const categoryLinks = document.querySelectorAll('#categories-list a[data-category-id]');
|
||||
categoryLinks.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
// Remove active class from all category links
|
||||
categoryLinks.forEach(c => c.classList.remove('active', 'text-primary'));
|
||||
// Add active class to clicked link
|
||||
this.classList.add('active', 'text-primary');
|
||||
|
||||
const categoryId = this.getAttribute('data-category-id');
|
||||
loadProducts({ 'filter[category]': categoryId });
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading categories:', error);
|
||||
document.getElementById('categories-list').innerHTML = '<li class="nav-item mb-1"><span class="text-danger">Error loading categories</span></li>';
|
||||
});
|
||||
}
|
||||
|
||||
function loadProducts(params = {}) {
|
||||
const container = document.getElementById('products-container');
|
||||
const countElement = document.getElementById('product-count');
|
||||
|
||||
// Show loading state
|
||||
container.innerHTML = '<div class="col-12 text-center py-5"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>';
|
||||
countElement.textContent = 'Loading...';
|
||||
|
||||
// Get current URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Add custom parameters
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] !== null) {
|
||||
urlParams.set(key, params[key]);
|
||||
}
|
||||
});
|
||||
|
||||
// Make AJAX request
|
||||
fetch(`{{ route('product.ajax') }}?${urlParams.toString()}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
container.innerHTML = data.products;
|
||||
countElement.textContent = data.count;
|
||||
|
||||
// Update filters section
|
||||
updateFiltersSection(data.filters);
|
||||
|
||||
// Update URL without page reload
|
||||
const newUrl = window.location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : '');
|
||||
window.history.pushState({ path: newUrl }, '', newUrl);
|
||||
} else {
|
||||
container.innerHTML = '<div class="col-12 text-center py-5 text-danger">Error loading products</div>';
|
||||
countElement.textContent = '0';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
container.innerHTML = '<div class="col-12 text-center py-5 text-danger">Error loading products</div>';
|
||||
countElement.textContent = '0';
|
||||
});
|
||||
}
|
||||
|
||||
function updateFiltersSection(filters) {
|
||||
const filtersContainer = document.querySelector('.filter-sidebar');
|
||||
const filtersSection = filtersContainer.querySelector('.pb-4.mb-2.mb-xl-3');
|
||||
|
||||
if (Object.keys(filters).length > 0) {
|
||||
// Build filters HTML
|
||||
let filtersHtml = `
|
||||
<div class="pb-4 mb-2 mb-xl-3">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<h4 class="h6 mb-0">Filter</h4>
|
||||
<a type="button" href="#"
|
||||
class="btn btn-sm btn-secondary bg-transparent border-0 text-decoration-underline p-0 ms-2 clear-all-filters">
|
||||
Clear all
|
||||
</a>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
`;
|
||||
|
||||
Object.keys(filters).forEach(key => {
|
||||
filtersHtml += `
|
||||
<button type="button" class="btn btn-sm btn-secondary remove-filter"
|
||||
data-filter-key="${key}">
|
||||
<i class="ci-close fs-sm ms-n1 me-1"></i>
|
||||
${filters[key]}
|
||||
</button>
|
||||
`;
|
||||
});
|
||||
|
||||
filtersHtml += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Update or create filters section
|
||||
if (filtersSection) {
|
||||
filtersSection.outerHTML = filtersHtml;
|
||||
} else {
|
||||
filtersContainer.insertAdjacentHTML('afterbegin', filtersHtml);
|
||||
}
|
||||
|
||||
// Re-attach event listeners for new filter buttons
|
||||
attachFilterEventListeners();
|
||||
} else {
|
||||
// Remove filters section if no filters
|
||||
if (filtersSection) {
|
||||
filtersSection.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function attachFilterEventListeners() {
|
||||
// Handle filter removal
|
||||
const removeFilterButtons = document.querySelectorAll('.remove-filter');
|
||||
removeFilterButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const filterKey = this.getAttribute('data-filter-key');
|
||||
removeFilter(filterKey);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle clear all filters
|
||||
const clearAllButton = document.querySelector('.clear-all-filters');
|
||||
if (clearAllButton) {
|
||||
clearAllButton.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
clearAllFilters();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
|
|
|
|||
|
|
@ -24,4 +24,7 @@ Route::post('/locale/switch', [LocaleController::class, 'switch'])->name('locale
|
|||
Route::get('/', [HomeController::class, 'index'])->name('home');
|
||||
|
||||
Route::get('/products',[ProductController::class, 'index'])->name('product.index');
|
||||
Route::get('/products/ajax',[ProductController::class, 'ajax'])->name('product.ajax');
|
||||
Route::get('/products/ajax/categories',[ProductController::class, 'categories'])->name('product.ajax.categories');
|
||||
Route::get('/products/ajax/genders',[ProductController::class, 'genders'])->name('product.ajax.genders');
|
||||
Route::get('/product/{slug}',[ProductController::class, 'detail'])->name('product.detail');
|
||||
Loading…
Reference in New Issue