Fix product Filter

Fixes #39
Fixes #35
Fixes #36
This commit is contained in:
Jérémy Munsch 2023-09-08 17:38:56 +02:00
parent 615263f233
commit 8e48fc5ea1
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
4 changed files with 208 additions and 126 deletions

View file

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\Products\IndexFilterRequest;
use App\Http\Requests\Products\StoreProductRequest;
use App\Http\Requests\Products\UpdateProductRequest;
use App\Jobs\ExportProduct;
@ -12,7 +13,6 @@ use App\Models\Product;
use App\Traits\Controllers\HasDocuments;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -23,27 +23,29 @@ class ProductController extends Controller
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @param \App\Http\Requests\Products\IndexFilterRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
public function index(IndexFilterRequest $request)
{
// DB::enableQueryLog();
/** @var \Illuminate\Database\Eloquent\Builder $brands */
$brands = Brand::query();
$brandsQuery = Brand::query();
/** @var \Illuminate\Database\Eloquent\Builder $products */
$products = Product::query();
/** @var \Illuminate\Database\Eloquent\Builder $productsQuery */
$productsQuery = Product::query();
$this->requestFilters($products, $request);
$this->requestFilters($request, $productsQuery);
$this->sortQuery($products);
$this->sortQuery($productsQuery);
$products = $this->paginate($products);
$products = $this->paginate($productsQuery);
// dd(DB::getQueryLog(), $products->items());
/** @var array $printMultiProduct Array of id */
$printMultiProduct = session('printMultiProduct') ?? [];
$brands = $brands->get();
$brands = $brandsQuery->get();
$keywords = Keyword::query()->where('keyword_cat', 'LIKE', 'product_%')->get();
@ -51,8 +53,7 @@ class ProductController extends Controller
'products' => $products,
'brands' => $brands,
'keywords' => $keywords,
'printMultiProduct' => $printMultiProduct,
'queryParameters' => $request->all(),
'printMultiProduct' => $printMultiProduct
]);
}
@ -385,47 +386,60 @@ class ProductController extends Controller
}
/**
* Show the form for creating a new resource.
* Applies filters from products.index
*
* @param \Illuminate\Database\Eloquent\Builder $products
* @param \Illuminate\Http\Request $request
* @param \App\Http\Requests\Products\IndexFilterRequest $request
* @param \Illuminate\Database\Eloquent\Builder $productsQuery
* @return \Illuminate\Contracts\View\View
* @phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
private function requestFilters(Builder $products, Request $request): \Illuminate\Database\Eloquent\Builder
{
private function requestFilters(
IndexFilterRequest $request,
Builder $productsQuery
): \Illuminate\Database\Eloquent\Builder {
//phpcs:enable
$queryFetch = ['name', 'claims', 'origins', 'keywords', 'date_before', 'date_after'];
foreach (Arr::only($request->all(), $queryFetch) as $key => $queryParameter) {
if ($request->sort_col === null && $request->sort_way === null && $request->rst === null) {
$products->when(
isset($queryParameter) && $queryParameter != 0,
function ($q) use ($queryParameter, $key) {
if ($key === "name") {
$q->where($key, 'LIKE', '%' . $queryParameter . '%');
} elseif ($key === "claims" || $key === "origins") {
foreach ($queryParameter as $item) {
$q->where($key, 'LIKE', '%{"id":"' . $item['value'] . '"}%');
}
} elseif ($key === "keywords") {
foreach ($queryParameter as $keyword) {
$q->whereHas('productKeywords', function ($q) use ($keyword, $key) {
$q->where($key . '.id', intval($keyword['value']));
});
}
} elseif ($key === "date_before" || $key === "date_after") {
if ($key === "date_before") {
$q->whereDate('created_at', '>=', $queryParameter);
} else {
$q->whereDate('created_at', '<=', $queryParameter);
}
} else {
$q->where($key, $queryParameter);
} //end if
}
);
} //end if
foreach ($request->validated() as $parameterName => $queryParameter) {
$productsQuery->when(
!empty($queryParameter),
function (Builder $q) use ($queryParameter, $parameterName) {
switch ($parameterName) {
// * String type parameters (Approx search)
case 'name':
case 'shop':
case 'fair':
case 'catalogue':
case 'specified_claim':
$q->where($parameterName, 'LIKE', "%{$queryParameter}%");
break;
// * Number type parameters (Approx search)
case 'gencode':
$q->where($parameterName, \intval($queryParameter));
break;
// * Date type parameters
case 'date_before':
$q->whereDate('created_at', '>=', $queryParameter);
break;
case 'date_after':
$q->whereDate('created_at', '<=', $queryParameter);
break;
// * Enums Array
case 'claims':
case 'origins':
$q->whereJsonContains($parameterName, \collect($queryParameter)
->map(fn ($enumValue) => \intval($enumValue)));
break;
// * RelationShips parameters
case 'keywords':
$q->whereHas('productKeywords', function (Builder $subQ) use ($queryParameter) {
$subQ->whereIn('keywords.id', $queryParameter);
});
break;
default:
$q->where($parameterName, $queryParameter);
} //end switch
}
);
} //end foreach
return $products;
return $productsQuery;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace App\Http\Requests\Products;
use App\Enums\Products\CertificationEnum;
use App\Enums\Products\ClaimEnum;
use App\Enums\Products\DistributionNetworkEnum;
use App\Enums\Products\GeoAreaEnum;
use App\Enums\Products\OriginEnum;
use App\Enums\Products\RangeEnum;
use App\Enums\Products\RayEnum;
use App\Enums\Products\SourceEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
class IndexFilterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'name' => 'nullable|string',
'brand_id' => 'nullable|exists:brands,id',
'gencod' => 'nullable|string',
'source' => ['nullable', new Enum(SourceEnum::class)],
'shop' => 'nullable|string',
'fair' => 'nullable|string',
'catalogue' => 'nullable|string',
'claims' => 'nullable|array',
'claims.*' => ['required', new Enum(ClaimEnum::class)],
'specified_claim' => 'nullable|string',
'origins' => 'nullable|array',
'origins.*' => ['required', new Enum(OriginEnum::class)],
'certification' => ['nullable', new Enum(CertificationEnum::class)],
'distribution_network' => ['nullable', new Enum(DistributionNetworkEnum::class)],
'ray' => ['nullable', new Enum(RayEnum::class)],
'product_range' => ['nullable', new Enum(RangeEnum::class)],
'geo_area' => ['nullable', new Enum(GeoAreaEnum::class)],
'keywords' => 'nullable|array',
'keywords.*' => 'required|exists:keywords,id',
'date_before' => 'nullable|date:Y-m-d',
'date_after' => 'nullable|date:Y-m-d',
];
}
}

View file

@ -41,7 +41,7 @@
<input
:name="`${intName}[${String(index)}]`"
type="hidden"
:value="sItem.id"
:value="sItem.value"
>
</template>
<small class="form-text text-muted">{{ intHelper }}</small>
@ -149,13 +149,13 @@ export default defineComponent({
parseTags(intSelectedItem: number[]): CustomTag[] {
const out: CustomTag[] = [];
intSelectedItem.forEach((intItem: number) => {
const selectedItem = this.intAllItems.find(item => String(item.value) === String(intItem));
const selectedItem = this.intAllItems.find(item => String(item.id ?? item.value) === String(intItem));
if (!selectedItem) {
console.debug(intItem);
throw new Error(`Could not find enum ${intItem}`);
}
out.push({
id: String(selectedItem.value),
id: String(selectedItem.id ?? selectedItem.value),
value: String(selectedItem.value),
text: String(selectedItem.text)
} as LaravelModel as CustomTag);

View file

@ -1,10 +1,10 @@
@php
$iArgs = [];
if(request()->sort_col) {
if (request()->sort_col) {
$iArgs['sort_col'] = request()->sort_col;
}
if(request()->search) {
if (request()->search) {
$iArgs['search'] = request()->sort_way;
}
@endphp
@ -12,173 +12,181 @@
<div class="col-12">
<div class="card">
<div class="card-header border-0 p-0">
<button class="btn border-0 dropdown-toggle w-100" type="button" data-bs-toggle="collapse" data-bs-target="#collapseProductsFilters" aria-expanded="false" aria-controls="collapseExample">
<button class="btn dropdown-toggle w-100 border-0" data-bs-toggle="collapse" data-bs-target="#collapseProductsFilters"
type="button" aria-expanded="false" aria-controls="collapseExample">
{{ __('Filtres') }}
</button>
</div>
<div class="border-top collapse" id="collapseProductsFilters">
<div class="card-body">
<form id="searchForm" action="{{ request()->url() }}">
{{-- ALWAYS RESET SEARCH TO PAGE 1 --}}
<input name="page" type="hidden" value="1">
<!-- GENERAL INFORMATIONS -->
<div class="row w-100 mx-auto">
<div class="col-12 col-md-6">
<input name="name" type="text" class="form-control" placeholder="Nom" value="{{ $queryParameters['name'] ?? null }}">
<input class="form-control" name="name" type="text" value="{{ request()->get('name') }}"
placeholder="Nom">
</div>
<div class="col-12 col-md-6 mt-1 m-md-0">
<div class="col-12 col-md-6 m-md-0 mt-1">
<select class="form-select" name="brand_id">
<option value="0">{{ __('Marque') }}</option>
@foreach($brands as $key => $brand)
<option @if($queryParameters['brand_id'] ?? null == $brand->id) selected @endif value="{{ $brand->id }}">{{ $brand->name }}</option>
<option value="">{{ __('Marque') }}</option>
@foreach ($brands as $key => $brand)
<option value="{{ $brand->id }}" @if (request()->get('brand_id') == $brand->id) selected @endif>
{{ $brand->name }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-6 mt-1">
<input name="gencod" type="text" class="form-control" placeholder="GENCOD" value="{{ $queryParameters['gencod'] ?? null }}">
<input class="form-control" name="gencod" type="text" value="{{ request()->get('gencod') }}"
placeholder="GENCOD">
</div>
</div>
<!-- SOURCE -->
<div class="row w-100 pt-3 mt-3 mx-auto border-top">
{{-- FIXME: Pourquoi dans le modèle il est Set une source, mais dans la recherche 3 champs complémentaire
peuvent etre utilisés, dans le modele également => il n'est pas nécessaire de cacher les
champs complémentaires dans le formulaire ? --}}
<div class="row w-100 border-top mx-auto mt-3 pt-3">
<div class="col-12 col-md-6">
<select class="form-select" name="source">
<option value="0">{{ __('Source') }}</option>
@foreach(\App\Enums\Products\SourceEnum::toArray() as $key => $source)
<option @if(intval($queryParameters['source'] ?? 0) === $source->value) selected @endif value="{{ $source->value }}">{{ $source->label }}</option>
<option value="">{{ __('Source') }}</option>
@foreach (\App\Enums\Products\SourceEnum::toArray() as $key => $source)
<option value="{{ $source->value }}" @if (request()->get('source') == $source->value) selected @endif>
{{ $source->label }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-6 mt-1 m-md-0">
<input name="shop" type="text" class="form-control" placeholder="Magasin" value="{{ $queryParameters['shop'] ?? null }}">
<div class="col-12 col-md-6 m-md-0 mt-1">
<input class="form-control" name="shop" type="text" value="{{ request()->get('shop') }}"
placeholder="{{ __('Magasin') }}">
</div>
<div class="col-12 col-md-6 mt-1">
<input name="fair" type="text" class="form-control" placeholder="Salon" value="{{ $queryParameters['fair'] ?? null }}">
<input class="form-control" name="fair" type="text" value="{{ request()->get('fair') }}"
placeholder="{{ __('Salon') }}">
</div>
<div class="col-12 col-md-6 mt-1">
<input name="catalogue" type="text" class="form-control" placeholder="Catalogue" value="{{ $queryParameters['catalogue'] ?? null }}">
<input class="form-control" name="catalogue" type="text" value="{{ request()->get('catalogue') }}"
placeholder="{{ __('Catalogue') }}">
</div>
</div>
<!-- CLAIMS -->
<div class="row w-100 pt-3 mt-3 mx-auto border-top">
<div class="row w-100 border-top mx-auto mt-3 pt-3">
<div class="col-12 col-md-6">
@php
$itemsList = collect(\App\Enums\Products\ClaimEnum::toArray())->values();
$values = collect($queryParameters['claims'] ?? [])->values();
$selectedItemList = collect($itemsList)->filter(fn($claim) =>
$values->filter(fn($value) => intval($value['value']) === $claim->value)->count())->values();
@endphp
@include('partials.inputs.select-multiple-enum', [
'id' => 'productsClaim',
'name' => 'claims',
'itemsSelected' => $selectedItemList,
'itemsList' => $itemsList,
'itemLimit' => [0, count($itemsList)],
'itemsSelected' => collect(request()->get('claims', []))->filter(fn($value) => is_numeric($value)),
'itemsList' => collect(\App\Enums\Products\ClaimEnum::toArray())->values(),
'itemLimit' => [0, count(\App\Enums\Products\ClaimEnum::toArray())],
'placeholder' => __('Recherchez une Allégation'),
'simpleComponent' => true,
])
</div>
<div class="col-12 col-md-6 mt-1 m-md-0">
<input name="specified_claim" type="text" class="form-control" placeholder="Allégation complémentaire" value="{{ $queryParameters['specified_claim'] ?? null }}">
<div class="col-12 col-md-6 m-md-0 mt-1">
<input class="form-control" name="specified_claim" type="text"
value="{{ request()->get('specified_claim') }}" placeholder="Allégation complémentaire">
</div>
</div>
<!-- OTHERS -->
<div class="row w-100 pt-3 mt-3 mx-auto border-top">
<div class="row w-100 border-top mx-auto mt-3 pt-3">
<div class="col-12 col-md-6">
@php
$itemsList = collect(\App\Enums\Products\OriginEnum::toArray())->values();
$values = collect($queryParameters['origins'] ?? [])->values();
$selectedItemList = collect($itemsList)->filter(fn($origin) =>
$values->filter(fn($value) => intval($value['value']) === $origin->value)->count())->values();
@endphp
@include('partials.inputs.select-multiple-enum', [
'id' => 'productsOrigin',
'name' => 'origins',
'itemsSelected' => $selectedItemList,
'itemsList' => $itemsList,
'itemLimit' => [0, count($itemsList)],
'itemsSelected' => collect(request()->get('origins', []))->filter(
fn($value) => is_numeric($value)),
'itemsList' => collect(\App\Enums\Products\OriginEnum::toArray())->values(),
'itemLimit' => [0, count(\App\Enums\Products\OriginEnum::toArray())],
'placeholder' => __('Recherchez une Origine'),
'simpleComponent' => true,
])
</div>
<div class="col-12 col-md-6 mt-1 m-md-0">
<div class="col-12 col-md-6 m-md-0 mt-1">
<select class="form-select" name="certification">
<option value="0">{{ __('Certification') }}</option>
@foreach(\App\Enums\Products\CertificationEnum::toArray() as $key => $certification)
<option @if(intval($queryParameters['certification'] ?? 0) == $certification->value) selected @endif value="{{ $certification->value }}">{{ $certification->label }}</option>
<option value="">{{ __('Certification') }}</option>
@foreach (\App\Enums\Products\CertificationEnum::toArray() as $key => $certification)
<option value="{{ $certification->value }}" @if (request()->get('certification') == $certification->value) selected @endif>
{{ $certification->label }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-6 mt-1">
<select class="form-select" name="distribution_network">
<option value="0">{{ __('Réseau de distribution') }}</option>
@foreach(\App\Enums\Products\DistributionNetworkEnum::toArray() as $key => $distribution_network)
<option @if(intval($queryParameters['distribution_network'] ?? 0) == $distribution_network->value) selected @endif value="{{ $distribution_network->value }}">{{ $distribution_network->label }}</option>
<option value="">{{ __('Réseau de distribution') }}</option>
@foreach (\App\Enums\Products\DistributionNetworkEnum::toArray() as $key => $distribution_network)
<option value="{{ $distribution_network->value }}"
@if (request()->get('distribution_network') == $distribution_network->value) selected @endif>{{ $distribution_network->label }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-6 mt-1">
<select class="form-select" name="ray">
<option value="0">{{ __('Rayons') }}</option>
@foreach(\App\Enums\Products\RayEnum::toArray() as $key => $ray)
<option @if(intval($queryParameters['ray'] ?? 0) == $ray->value) selected @endif value="{{ $ray->value }}">{{ $ray->label }}</option>
<option value="">{{ __('Rayons') }}</option>
@foreach (\App\Enums\Products\RayEnum::toArray() as $key => $ray)
<option value="{{ $ray->value }}" @if (request()->get('ray') == $ray->value) selected @endif>
{{ $ray->label }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-6 mt-1">
<select class="form-select" name="product_range">
<option value="0">{{ __('Famille de produit') }}</option>
@foreach(\App\Enums\Products\RangeEnum::toArray() as $key => $product_range)
<option @if(intval($queryParameters['product_range'] ?? 0) == $product_range->value) selected @endif value="{{ $product_range->value }}">{{ $product_range->label }}</option>
<option value="">{{ __('Famille de produit') }}</option>
@foreach (\App\Enums\Products\RangeEnum::toArray() as $key => $product_range)
<option value="{{ $product_range->value }}" @if (request()->get('product_range') == $product_range->value) selected @endif>
{{ $product_range->label }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-6 mt-1">
<select class="form-select" name="geo_area">
<option value="0">{{ __('Secteur') }}</option>
@foreach(\App\Enums\Products\GeoAreaEnum::toArray() as $key => $geo_area)
<option @if(intval($queryParameters['geo_area'] ?? 0) == $geo_area->value) selected @endif value="{{ $geo_area->value }}">{{ $geo_area->label }}</option>
<option value="">{{ __('Secteur') }}</option>
@foreach (\App\Enums\Products\GeoAreaEnum::toArray() as $key => $geo_area)
<option value="{{ $geo_area->value }}" @if (request()->get('product_range') == $geo_area->value) selected @endif>
{{ $geo_area->label }}</option>
@endforeach
</select>
</div>
</div>
<!-- KEYWORDS -->
<div class="row w-100 pt-3 mt-3 mx-auto border-top">
<div class="row w-100 border-top mx-auto mt-3 pt-3">
<div class="col-12 col-md-6">
@php
$itemsList = collect($keywords)->map(function ($keyword) {
return ["name" => $keyword->keyword_cat->value, "label" => $keyword->label, "value" => $keyword->id];
})->values();
$values = collect($queryParameters['keywords'] ?? [])->values();
$selectedItemList = collect($itemsList)->filter(fn($keyword) =>
$values->filter(fn($value) => intval($value['value']) === $keyword['value'])->count())->values();
return ['name' => $keyword->keyword_cat->value, 'label' => $keyword->label, 'value' => $keyword->id];
});
@endphp
@include('partials.inputs.select-multiple-enum', [
'id' => 'productsKeywordsn',
'id' => 'productsKeywords',
'name' => 'keywords',
'itemsSelected' => $selectedItemList,
'itemsList' => $itemsList,
'itemLimit' => [0, count($itemsList)],
'placeholder' => __('Recherchez un mot clé'),
'itemsSelected' => collect(request()->get('keywords', []))->filter(
fn($value) => is_numeric($value)),
'itemsList' => $itemsList->values(),
'itemLimit' => [0, $itemsList->count()],
'placeholder' => __('Recherchez une tendance'),
'simpleComponent' => true,
])
</div>
</div>
<!-- DATES -->
<div class="row w-100 pt-3 mt-3 mx-auto border-top">
<div class="row w-100 border-top mx-auto mt-3 pt-3">
<div class="col-12 col-md-6">
<label class="text-muted fst-italic">{{ __('Saisie après le :') }}</label>
<input name="date_before" type="date" class="form-control" value="{{ $queryParameters['date_before'] ?? null }}">
</div>
<div class="col-12 col-md-6 mt-1 m-md-0">
<label class="text-muted fst-italic">{{ __('Saisie avant le :') }}</label>
<input name="date_after" type="date" class="form-control" value="{{ $queryParameters['date_after'] ?? null }}">
<input class="form-control" name="date_before" type="date"
value="{{ request()->query('date_before') }}">
</div>
<div class="col-12 col-md-6 m-md-0 mt-1">
<label class="text-muted fst-italic">{{ __('Saisie après le :') }}</label>
<input class="form-control" name="date_after" type="date"
value="{{ request()->query('date_after') }}">
</div>
</div>
<!-- BUTTONS -->
<div class="d-flex justify-content-center input-group pt-3 mt-3 border-top">
<button type="submit" class="btn btn-primary" data-bs="tooltip" title="{{ __('Lancer la recherche') }}">
<div class="d-flex justify-content-center input-group border-top mt-3 pt-3">
<button class="btn btn-primary" data-bs="tooltip" type="submit" title="{{ __('Lancer la recherche') }}">
{{ __('Rechercher') }}
</button>
<a class="p-0" href="{{ request()->url() }}">
<button type="button" data-bs="tooltip" class="btn btn-danger rounded-0 rounded-end"
<button class="btn btn-danger rounded-0 rounded-end" data-bs="tooltip" type="button"
title="{{ __('Effacer la recherche') }}">
<i class="fas fa-backspace"></i>
</button>