From c71b7ff5c259f75c376796d33e085d31abcfb71f Mon Sep 17 00:00:00 2001 From: Bayu Lukman Yusuf Date: Thu, 8 Jan 2026 12:54:55 +0700 Subject: [PATCH] filter with ajax --- app/Http/Controllers/ProductController.php | 24 +- .../Catalog/ProductRepository.php | 283 ++++--- lang/en/catalog_fashion.php | 1 + lang/id/catalog_fashion.php | 1 + .../components/home/product-card.blade.php | 4 +- .../views/shop/catalog-fashion.blade.php | 754 ++++++++++++------ 6 files changed, 703 insertions(+), 364 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 8256de0..52278fb 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -66,6 +66,7 @@ class ProductController extends Controller $page = $request->page ?? 1; $search = $request->search; + $filter = $request->filter ?? []; $sortBy = $request->sort_by ?? 'relevance'; @@ -102,15 +103,23 @@ class ProductController extends Controller 'price_range_end' => $price_range_end, ]); + // Check if there are more products + $hasMore = count($products) >= $limit; + // Render product cards HTML $productHtml = ''; - foreach ($products as $product) { - $productHtml .= '
'; - $productHtml .= view('components.home.product-card', ['product' => $product])->render(); + if (count($products) == 0) { + $productHtml = '
'; + $productHtml .= 'Pencarian tidak ditemukan'; $productHtml .= '
'; + } else { + foreach ($products as $product) { + $productHtml .= '
'; + $productHtml .= view('components.home.product-card', ['product' => $product])->render(); + $productHtml .= '
'; + } } - // filter $filter = $request->filter ?? []; if (isset($filter['category']) && $filter['category']){ @@ -123,7 +132,6 @@ class ProductController extends Controller } } - if (isset($filter['gender']) && $filter['gender']) { $gender = Gender::find($filter['gender']); @@ -141,13 +149,13 @@ class ProductController extends Controller 'filters' => $filters, 'products' => $productHtml, 'count' => count($products), - 'has_more' => count($products) >= $limit + 'has_more' => $hasMore, + 'current_page' => $page ]); } + public function index(Request $request) { - - $productRepository = new ProductRepository; $products = []; diff --git a/app/Repositories/Catalog/ProductRepository.php b/app/Repositories/Catalog/ProductRepository.php index 1eb75c5..0c323cc 100644 --- a/app/Repositories/Catalog/ProductRepository.php +++ b/app/Repositories/Catalog/ProductRepository.php @@ -6,83 +6,57 @@ use App\Models\DiscountItem; use App\Models\Items; use App\Models\StoreCategoryMap; use Illuminate\Support\Facades\DB; -use Carbon\Carbon; +use Illuminate\Support\Facades\Log; class ProductRepository { public function getList($params) - { - $event = @$params["event"]; - $sort = @$params["sort"]; - $search = @$params["search"]; - $category_id = @$params["category_id"]; - $brand_id = @$params["brand_id"]; - $gender_id = @$params["gender_id"]; - $limit = @$params["limit"]; + { + $event = @$params['event']; + $sort = @$params['sort']; + $search = @$params['search']; + $category_id = @$params['category_id']; + $brand_id = @$params['brand_id']; + $gender_id = @$params['gender_id']; + $limit = @$params['limit']; - $location_id = @$params["location_id"]; - $is_consignment = @$params["is_consignment"]; - - $price_range_start = @$params["price_range_start"]; - $price_range_end = @$params["price_range_end"]; + $location_id = @$params['location_id']; + $is_consignment = @$params['is_consignment']; + $price_range_start = @$params['price_range_start']; + $price_range_end = @$params['price_range_end']; $sorting_ids = []; - if ($sort == "price_low_to_high" || $sort == "price_high_to_low"){ - $sorting_ids = DiscountItem::whereHas('discount', function($query) use ($location_id, $is_consignment) { - $query->where('type', 'price') - ->where(function ($query) { - $query->where('valid_at', '<=', now()) - ->orWhereNull('valid_at'); - }) - ->where(function ($query) { - $query->where('expired_at', '>', now()) - ->orWhereNull('expired_at'); - }) - ->where(function ($query) use ($location_id, $is_consignment) { - if (! $is_consignment) { - $query->whereNull('discounts.location_id'); - } - if ($location_id) { - $query->orWhere('discounts.location_id', $location_id); - } - }); - }) - ->orderBy('discount_items.price', $sort == "price_low_to_high" ? 'asc' : 'desc') - ->pluck('item_reference_id')->toArray(); + if ($sort == 'price_low_to_high' || $sort == 'price_high_to_low') { + $params_filter = []; + $params_filter['location_id'] = $location_id; + $params_filter['sort'] = $sort; + $sorting_ids = collect($this->getProductList($params_filter))->pluck('id')->toArray(); + + // Log::info($sorting_ids); } $where_ids = []; if ($price_range_start && $price_range_end) { - $where_ids = DiscountItem::whereHas('discount', function($query) use ($location_id, $is_consignment, $price_range_start, $price_range_end) { - $query->where('type', 'price') - ->where(function ($query) { - $query->where('valid_at', '<=', now()) - ->orWhereNull('valid_at'); - }) - ->where(function ($query) { - $query->where('expired_at', '>', now()) - ->orWhereNull('expired_at'); - }) - ->where(function ($query) use ($location_id, $is_consignment) { - if (! $is_consignment) { - $query->whereNull('discounts.location_id'); - } + $params_filter = []; + $params_filter['location_id'] = $location_id; + $params_filter['price_range_start'] = $price_range_start; + $params_filter['price_range_end'] = $price_range_end; + + $where_ids = collect($this->getProductList($params_filter))->pluck('id')->toArray(); + + if (! empty($sorting_ids) && count($sorting_ids) > 0) { + $where_ids = array_values( + array_intersect($sorting_ids, $where_ids) + ); + } - if ($location_id) { - $query->orWhere('discounts.location_id', $location_id); - } - }) - ->where('discount_items.price', '>=', $price_range_start) - ->where('discount_items.price', '<=', $price_range_end); - }) - ->pluck('item_reference_id')->toArray(); } - $builder = Items::select('items.*','percent') + $builder = Items::select('items.*', 'percent') ->leftJoin('item_dimension', 'item_dimension.no', 'items.number') - ->leftJoin(DB::raw("(select distinct on (item_id) item_id, percent, discounts.created_at, + ->leftJoin(DB::raw("(select distinct on (item_id) item_id, percent, discounts.created_at, case when discounts.valid_at is null then 'special-offer' else 'limited-sale' end as event from discount_items left join item_reference on item_reference_id = item_reference.id @@ -91,32 +65,38 @@ class ProductRepository ( discounts.valid_at is null or discounts.valid_at <= now()) and ( discounts.valid_at is null or discounts.expired_at >= now()) order by item_id, discounts.created_at desc - ) as d"),"d.item_id","=","items.id") - ->when(true, function($query) use ($event, $sort,$sorting_ids){ - if ($event){ - $query->orderByRaw("case when brand = 'CHAMELO' then 1 else 2 end ASC "); + ) as d"), 'd.item_id', '=', 'items.id') + ->when(true, function ($query) use ($event, $sort, $sorting_ids) { + if ($event) { + $query->orderByRaw("case when brand = 'CHAMELO' then 1 else 2 end ASC "); - $query->orderBy("percent", "desc"); - }else{ + $query->orderBy('percent', 'desc'); + } else { - /* if ($event == "special-offer"){ - $query->orderByRaw("case when brand = 'CHAMELO' then 1 else 2 end ASC "); - } */ + if (! empty($sorting_ids) && count($sorting_ids) > 0) { + + $caseStatements = []; + foreach ($sorting_ids as $index => $id) { + $caseStatements[] = "WHEN items.id = {$id} THEN " . ($index + 1); + } + $caseStatements[] = "ELSE 999"; + + $query->orderByRaw(" + CASE + " . implode("\n ", $caseStatements) . " + END ASC + "); - if (!empty($sorting_ids)) { - $ids = implode(',', $sorting_ids); - $query->orderByRaw("array_position(ARRAY[$ids], items.id)"); - }else if ($sort == "new"){ - $query->orderByRaw("case when category1 in ('CLUBS','CLUB','COMPONENT HEAD') and brand = 'PXG' then 1 else 2 end ASC"); - } else { - $query->orderByRaw("case when d.created_at is not null then 1 else 2 end ASC, random()"); - } + } elseif ($sort == 'new') { + $query->orderByRaw("case when category1 in ('CLUBS','CLUB','COMPONENT HEAD') and brand = 'PXG' then 1 else 2 end ASC"); + } else { + $query->orderByRaw('case when d.created_at is not null then 1 else 2 end ASC, random()'); + } - // $query->orderByRaw("items.created_at desc NULLS LAST"); - } + } - }) - ->where("is_publish", true) + }) + ->where('is_publish', true) ->when($search, function ($query) use ($search) { $query->where(function ($query) use ($search) { $query->where('number', 'ILIKE', "%$search%") @@ -130,8 +110,11 @@ class ProductRepository ->when($gender_id, function ($query) use ($gender_id) { $query->where('gender_id', $gender_id); }) - ->when($event, function($query) use ($event){ + ->when($event, function ($query) use ($event) { $query->where('d.event', $event); + }) + ->when($where_ids, function ($query) use ($where_ids) { + $query->whereIn('items.id', $where_ids); }); if ($category_id) { @@ -155,10 +138,128 @@ class ProductRepository }); } - return $builder->paginate($limit); } + public function getProductList($params) + { + $bindings = []; + + // ========================= + // ORDER BY + // ========================= + $order_by = 'ORDER BY dpr.discount_price ASC NULLS LAST'; + + $sort = $params['sort'] ?? null; + if ($sort === 'price_high_to_low') { + $order_by = 'ORDER BY dpr.discount_price DESC NULLS LAST'; + } elseif ($sort === 'price_low_to_high') { + $order_by = 'ORDER BY dpr.discount_price ASC NULLS LAST'; + } elseif ($sort === 'new') { + $order_by = 'ORDER BY items.created_at DESC NULLS LAST'; + } + + $location_id = $params['location_id'] ?? null; + + // ========================= + // WHERE CONDITIONS + // ========================= + $wheres = []; + + $price_range_start = $params['price_range_start'] ?? null; + $price_range_end = $params['price_range_end'] ?? null; + + if ($price_range_start !== null && $price_range_end !== null) { + $wheres[] = '(dpr.discount_price BETWEEN ? AND ?)'; + $bindings[] = $price_range_start; + $bindings[] = $price_range_end; + } + + $whereSql = $wheres + ? 'AND '.implode(' AND ', $wheres) + : ''; + + // ========================= + // QUERY + // ========================= + $sql = "SELECT + items.*, + dp.percent, + dp.event, + dpr.discount_price + FROM items + LEFT JOIN item_dimension + ON item_dimension.no = items.number + + -- ========================= + -- DISCOUNT PERCENT + -- ========================= + LEFT JOIN ( + SELECT DISTINCT ON (di.item_reference_id) + di.item_reference_id, + di.percent, + d.created_at, + d.location_id, + CASE + WHEN d.valid_at IS NULL THEN 'special-offer' + ELSE 'limited-sale' + END AS event + FROM discount_items di + JOIN discounts d ON d.id = di.discount_id + WHERE d.type = 'discount' + AND (d.valid_at IS NULL OR d.valid_at <= NOW()) + AND (d.expired_at IS NULL OR d.expired_at >= NOW()) + AND ( + (22 IS NOT NULL AND (d.location_id = 22 OR d.location_id IS NULL)) + OR (22 IS NULL AND d.location_id IS NULL) + ) + ORDER BY + di.item_reference_id, + CASE + WHEN d.location_id = 22 THEN 1 + ELSE 2 + END, + d.created_at DESC + ) dp ON dp.item_reference_id = items.id + + -- ========================= + -- DISCOUNT PRICE + -- ========================= + LEFT JOIN ( + SELECT DISTINCT ON (di.item_reference_id) + di.item_reference_id, + di.price AS discount_price, + d.created_at, + d.location_id + FROM discount_items di + JOIN discounts d ON d.id = di.discount_id + WHERE d.type = 'price' + AND (d.valid_at IS NULL OR d.valid_at <= NOW()) + AND (d.expired_at IS NULL OR d.expired_at >= NOW()) + AND ( + (22 IS NOT NULL AND (d.location_id = 22 OR d.location_id IS NULL)) + OR (22 IS NULL AND d.location_id IS NULL) + ) + ORDER BY + di.item_reference_id, + CASE + WHEN d.location_id = 22 THEN 1 + ELSE 2 + END, + d.created_at DESC + ) dpr ON dpr.item_reference_id = items.id + + WHERE items.is_publish = TRUE + AND items.deleted_at IS NULL + $whereSql + $order_by + "; + + + $select= DB::select($sql, $bindings); + + return $select; + } public function getMinMaxPrice() { @@ -167,14 +268,14 @@ class ProductRepository 'max' => DiscountItem::max('price'), ]; } - - public function show($slug) { - $product = Items::where('slug', $slug)->first(); - - + $product = Items::where('slug', $slug) + ->when(is_int($slug), function ($query) use ($slug) { + return $query->orWhere('id', $slug); + }) + ->first(); if ($product == null) { abort(404); @@ -193,10 +294,10 @@ class ProductRepository if (empty($ids)) { return collect(); } - + // Remove except_ids from the list - $ids = array_diff($ids, (array)$except_ids); - + $ids = array_diff($ids, (array) $except_ids); + return Items::whereIn('id', $ids)->inRandomOrder()->take(10)->get(); } } diff --git a/lang/en/catalog_fashion.php b/lang/en/catalog_fashion.php index 839f0a9..d368cc2 100644 --- a/lang/en/catalog_fashion.php +++ b/lang/en/catalog_fashion.php @@ -1,6 +1,7 @@ "Catalog", 'categories' => 'Categories', 'genders' => 'Genders', 'price' => 'Price', diff --git a/lang/id/catalog_fashion.php b/lang/id/catalog_fashion.php index 549e36a..b662781 100644 --- a/lang/id/catalog_fashion.php +++ b/lang/id/catalog_fashion.php @@ -1,6 +1,7 @@ "Katalog", 'categories' => 'Kategori', 'genders' => 'Gender', 'price' => 'Harga', diff --git a/resources/views/components/home/product-card.blade.php b/resources/views/components/home/product-card.blade.php index 9bf2f6c..f1f9170 100644 --- a/resources/views/components/home/product-card.blade.php +++ b/resources/views/components/home/product-card.blade.php @@ -11,7 +11,7 @@ aria-label="Add to Wishlist"> - +
@@ -33,7 +33,7 @@
diff --git a/resources/views/shop/catalog-fashion.blade.php b/resources/views/shop/catalog-fashion.blade.php index 9710fa7..2be291f 100644 --- a/resources/views/shop/catalog-fashion.blade.php +++ b/resources/views/shop/catalog-fashion.blade.php @@ -1,8 +1,6 @@ @extends('layouts.landing', ['title' => 'Fashion Store - Catalog']) @section('content') - -
@@ -11,13 +9,13 @@ -

Shop catalog

+

{{ __('catalog_fashion.title') }}

@@ -34,16 +32,15 @@
- +

-

@@ -61,9 +58,8 @@

-

@@ -82,9 +78,8 @@

-

@@ -97,15 +92,15 @@
- - + +
- - + +
@@ -396,11 +391,10 @@
--}} -
+ {{--

-

@@ -427,7 +421,7 @@
-
+
--}} @@ -439,7 +433,8 @@
-
Found Loading... +
Found Loading... items
@@ -452,12 +447,20 @@ "containerInner": ["form-select", "border-0", "rounded-0", "px-1"] } }' - onchange="window.location.href='{{ route('product.index') }}?sort_by='+this.value+'&{{ http_build_query(request()->except('sort_by')) }}'"> - - - - - + onchange="window.location.href='{{ route('product.index') }}?sort_by='+this.value+'&{{ http_build_query(request()->except('sort_by')) }}'"> + + + + +
@@ -466,9 +469,9 @@
- {{--
+ {{--
+ class="position-relative text-center rounded-4 p-4 p-sm-5 py-md-4 py-xl-5">

Sweatshirts

Colors for your mood

--}} - -
- - - Show more - -
+ + + +
@@ -595,200 +598,428 @@ Filters + + + +@endsection + +@section('scripts') @endsection - -@section('scripts') -@endsection