$product_type = $request->productType;
$filterValues = $request->filter ?? [];
- $res = [];
- $type = ProductType::find($product_type);
- $rawproducts = Product::where('product_type', $product_type)->where('online', 1)->where('public', 1)->get();
- $products = [];
- foreach ($rawproducts as $idx => $rawproduct) {
- $products[$idx] = $rawproduct->getPageData();
- }
- $locale = App::getLocale();
-
- $specifications = $type->filters;
-
- $allmatches = [];
-
- $filters = Specification::whereIn('id', $specifications)->get();
- $res['filters'] = [];
- foreach ($filters as $filter) {
-
- $filterValue = null;
- if (isset($filterValues[$filter->id])) {
- $filterValue = explode(';', $filterValues[$filter->id]);
- sort($filterValue);
- }
-
- $specname = 's_' . Str::snake($filter->name);
- $data = $filter->getPageData();
- $f = ['label' => $data->label,
- 'id' => $data->id,
- 'type' => $data->type,
- ];
-
- $matching = [];
-
- if ($data->type == 'list') {
- $options = [];
- $values = [];
- foreach ($products as $product) {
- $v = $product->get($specname);
- if (!$v) {
- $v = '-';
- }
- if (!isset($values[$v])) {
- $values[$v] = 0;
- }
- $values[$v]++;
- if (null === $filterValue) {
- $matching[] = $product->id;
- } else {
- if (in_array($v, $filterValue)) {
- $matching[] = $product->id;
- }
- }
- }
- foreach ($data->options as $index => $option) {
- if (is_scalar($option)) {
- $o = $option;
- } else {
- $o = $option[$locale] ?? $option['fr'];
- }
- if ($o) {
- $nb = $values[$index] ?? 0;
- if ($nb > 0) {
- $options[] = ['label' => $o, 'value' => $index, 'nb_products' => $nb];
- }
- }
- }
- if (isset($values['-'])) {
- $options[] = ['label' => __('Non défini'), 'value' => '-', 'nb_products' => $values['-']];
- }
- $f['options'] = $options;
- } else if ($data->type == 'numeric' || $data->type == 'range') {
- $f['min'] = INF;
- $f['max'] = -INF;
- $f['unit'] = $data->unit;
-
- if ($data->type == 'numeric') {
- $f['prefix'] = $data['prefix'];
- } else {
- $f['prefix'] = '';
- }
-
- foreach ($products as $product) {
- $v = $product->get($specname);
- if ($data->type == 'range') {
- $f['min'] = min($f['min'], $v['first'] ?? INF, $v['second'] ?? INF);
- $f['max'] = max($f['max'], $v['first'] ?? -INF, $v['second'] ?? -INF);
- if (null !== $filterValue && !($v['first'] == '' && $v['second'] == '')) {
- $min = min($v['first'], $v['second']);
- $max = max($v['first'], $v['second']);
-
- if ($min <= $filterValue[1] && $max >= $filterValue[0]) {
- $matching[] = $product->id;
- }
- }
- } else {
- $f['min'] = min($f['min'], $v);
- $f['max'] = min($f['max'], $v);
- if (null !== $filterValue) {
- if ($v >= $filterValue[0] && $v <= $filterValue[0]) {
- $matching[] = $product->id;
- }
- }
- }
- if (null === $filterValue && $v != '') {
- $matching[] = $product->id;
- }
- }
- } else {
- continue;
- }
- $f['matching'] = ['count' => count($matching), 'hits' => $matching];
- $allmatches[] = $matching;
-
- $res['filters'][$data->id] = $f;
- }
-
- $intersection = call_user_func_array('array_intersect', $allmatches);
- $res['results'] = ['count' => count($intersection), 'hits' => $intersection];
- return $res;
+ return Product::getFilteredProducts($product_type, $filterValues);
}
}
public function productList(Request $request, $id)
{
$productType = ProductType::find($id);
+
if (!$productType) {
$this->_404();
}
$this->data['title'] = $productType->name;
-
$this->data['page'] = $this->data['product_type'] = $productType->getPageData();
- $this->data['products'] = [];
$products = Product::with('media') // Eager load media to avoid N+1 query problem
- ->where('product_type', $id)
+ ->where('product_type', $id)
->where('online', 1)
+ ->where('public', 1)
->get();
+ $this->data['products'] = [];
foreach ($products as $item) {
$this->data['products'][$item->id] = $item->getPageData();
}
+ // Get available filters
+ $filters = Product::getFilteredProducts($id);
+ $this->data['filters'] = $filters ? $filters['filters'] : []; // To be used by Vue component
+
return view('pages.products', $this->data);
}
return $cart_data;
}
+
+ public static function getFilteredProducts($product_type, $filter_values = []) {
+
+ $res = [];
+ $type = ProductType::find($product_type);
+
+ if (!$type || !$type->filters) {
+ return $res;
+ }
+
+ $raw_products = Product::where('product_type', $product_type)->where('online', 1)->where('public', 1)->get();
+ $products = [];
+ foreach ($raw_products as $idx => $raw_product) {
+ $products[$idx] = $raw_product->getPageData();
+ }
+ $locale = App::getLocale();
+
+ $specifications = $type->filters;
+
+ $all_matches = [];
+
+ $filters = Specification::whereIn('id', $specifications)->get();
+ $res['filters'] = [];
+ foreach ($filters as $filter) {
+
+ $filter_value = null;
+ if (isset($filter_values[$filter->id])) {
+ $filter_value = explode(';', $filter_values[$filter->id]);
+ sort($filter_value);
+ }
+
+ $spec_name = 's_' . Str::snake($filter->name);
+ $data = $filter->getPageData();
+ $f = ['label' => $data->label,
+ 'id' => $data->id,
+ 'type' => $data->type,
+ ];
+
+ $matching = [];
+
+ if ($data->type == 'list') {
+ $options = [];
+ $values = [];
+ foreach ($products as $product) {
+ $v = $product->get($spec_name);
+ if (!$v) {
+ $v = '-';
+ }
+ if (!isset($values[$v])) {
+ $values[$v] = 0;
+ }
+ $values[$v]++;
+ if (null === $filter_value) {
+ $matching[] = $product->id;
+ } else {
+ if (in_array($v, $filter_value)) {
+ $matching[] = $product->id;
+ }
+ }
+ }
+ foreach ($data->options as $index => $option) {
+ if (is_scalar($option)) {
+ $o = $option;
+ } else {
+ $o = $option[$locale] ?? $option['fr'];
+ }
+ if ($o) {
+ $nb = $values[$index] ?? 0;
+ if ($nb > 0) {
+ $options[] = ['label' => $o, 'value' => $index, 'nb_products' => $nb];
+ }
+ }
+ }
+ if (isset($values['-'])) {
+ $options[] = ['label' => __('Non défini'), 'value' => '-', 'nb_products' => $values['-']];
+ }
+ $f['options'] = $options;
+ } else if ($data->type == 'numeric' || $data->type == 'range') {
+ $f['min'] = INF;
+ $f['max'] = -INF;
+ $f['unit'] = $data->unit;
+
+ if ($data->type == 'numeric') {
+ $f['prefix'] = $data['prefix'];
+ } else {
+ $f['prefix'] = '';
+ }
+
+ foreach ($products as $product) {
+ $v = $product->get($spec_name);
+ if ($data->type == 'range') {
+ $f['min'] = min($f['min'], $v['first'] ?? INF, $v['second'] ?? INF);
+ $f['max'] = max($f['max'], $v['first'] ?? -INF, $v['second'] ?? -INF);
+ if (null !== $filter_value && !($v['first'] == '' && $v['second'] == '')) {
+ $min = min($v['first'], $v['second']);
+ $max = max($v['first'], $v['second']);
+
+ if ($min <= $filter_value[1] && $max >= $filter_value[0]) {
+ $matching[] = $product->id;
+ }
+ }
+ } else {
+ $f['min'] = min($f['min'], $v);
+ $f['max'] = min($f['max'], $v);
+ if (null !== $filter_value) {
+ if ($v >= $filter_value[0] && $v <= $filter_value[0]) {
+ $matching[] = $product->id;
+ }
+ }
+ }
+ if (null === $filter_value && $v != '') {
+ $matching[] = $product->id;
+ }
+ }
+ } else {
+ continue;
+ }
+ $f['matching'] = ['count' => count($matching), 'hits' => $matching];
+ $all_matches[] = $matching;
+
+ $res['filters'][$data->id] = $f;
+ }
+
+ $intersection = call_user_func_array('array_intersect', $all_matches);
+ $res['results'] = ['count' => count($intersection), 'hits' => $intersection];
+ return $res;
+ }
+
}
@php
- $technical_sheet=$product->getMediaUrl('technical_sheet',false)
+ $technical_sheet = $product->getMediaUrl('technical_sheet', false)
@endphp
@extends('layouts/app')
{{-- Product images --}}
<product-gallery
- :images='@json($product->getImageURLList('images', '', [asset('images/product-details/product-placeholder.svg')]))'
+ :images='@json($product->getImageURLList('images', '', [$product->getEntity()->image_fallback]))'
class="flex-grow"
style="max-width: 348px">
</product-gallery>
@if($technical_sheet)
<p class="mt-4">
- <a href="{{$technical_sheet}}">
+ <a href="{{ $technical_sheet }}">
{{ __('Télécharger la fiche produit') }}
</a>
</p>
@if(config('features.quote'))
<cart-add :product-id="{{ $product->id }}" class="align-middle">
- {{__('Ajouter à ma sélection')}}
+ {{ __('Ajouter à ma sélection') }}
<template v-slot:success-message>
@svg('tick', 'w-4 mr-3') {{ __('Produit ajouté') }}
@if ($product->specifications)
<tab name="{{ __('Spécifications') }}">
<dl>
- @foreach($product->getEntity()->getSpecificationsValues() as $title=>$value)
+ @foreach($product->getEntity()->getSpecificationsValues() as $title => $value)
<div>
- <dt>{{$title}}</dt>
- <dd>{{$value}}</dd>
+ <dt>{{ $title }}</dt>
+ <dd>{{ $value }}</dd>
</div>
@endforeach
</dl>
<ul>
@foreach($product->getEntity()->getDocuments() as $document)
<li>
- <a href="{{$document['media']->getUrl()}}" target="_blank"
+ <a href="{{ $document['media']->getUrl() }}" target="_blank"
class="flex items-center text-grey-dark hover:text-blue">
<img class="mr-4 mb-2"
src="{{asset('images/product-details/icon-'.$document['type'].'.svg')}}"
alt="">
- {{$document['label']}}
+ {{ $document['label'] }}
</a>
</li>
@endforeach
</content>
- @if(count($related)>0)
+ @if (count($related) > 0)
<full-width class="bg-grey-100">
<content>
<full-width class="bg-grey-100" padding="pt-1v pb-2v">
<content class="flex relative items-start">
- {{-- <div class="products-filters-wrapper sticky top-0 mr-1v pt-4 whitespace-no-wrap">--}}
+ <div class="products-filters-wrapper sticky top-0 mr-1v pt-4 whitespace-no-wrap">
- {{-- <div class="products-filters bg-white p-4">--}}
- {{-- <h3 class="text-base mb-2">Catégorie</h3>--}}
- {{-- @php--}}
- {{-- $categories = [--}}
- {{-- 'Capteur de force - Galette',--}}
- {{-- 'Capteur en "S" et miniatures',--}}
- {{-- 'Capteur bouton inox',--}}
- {{-- 'Conditionneurs et acquistion',--}}
- {{-- 'Capteur 3 axes',--}}
- {{-- 'Capteur 6 axes',--}}
- {{-- 'Plateforme 6 axes',--}}
- {{-- ];--}}
- {{-- @endphp--}}
+ <div class="products-filters bg-white p-4">
- {{-- @foreach ($categories as $i => $category)--}}
- {{-- <div class="flex justify-between py-1 text-sm">--}}
- {{-- <label for="cat_{{ $i }}">--}}
- {{-- <input type="checkbox" id="cat_{{ $i }}" class="mr-2">--}}
- {{-- {{ $category }}--}}
- {{-- </label>--}}
- {{-- <div class="products-filters-count pl-8">({{ rand(10, 95) }})</div>--}}
- {{-- </div>--}}
- {{-- @endforeach--}}
+ @foreach ($filters as $filter)
+ <h3 class="text-base mb-2">{{ $filter['label'] }}</h3>
- {{-- <hr class="h-px bg-grey-200 my-4">--}}
+ @if ($filter['type'] == 'list')
+ @foreach ($filter['options'] as $option)
+ <div class="flex justify-between py-1 text-sm">
+ <label>
+ <input type="checkbox" name="filter[{{ $filter['id'] }}][]" value="{{ $option['value'] }}" class="mr-2">
+ {{ $option['label'] }}
+ </label>
+ <div class="products-filters-count pl-8">({{ $option['nb_products'] }})</div>
+ </div>
+ @endforeach
+ @endif
- {{-- <h3 class="text-base mb-2">Filtre 01</h3>--}}
- {{-- @for ($i = 1; $i <= 5; $i++)--}}
- {{-- <div class="flex justify-between py-1 text-sm">--}}
- {{-- <label for="filter_1_{{ $i }}">--}}
- {{-- <input type="checkbox" id="filter_1_{{ $i }}" class="mr-2">--}}
- {{-- Option 0{{ $i }}--}}
- {{-- </label>--}}
- {{-- <div class="products-filters-count pl-8">({{ rand(10, 95) }})</div>--}}
- {{-- </div>--}}
- {{-- @endfor--}}
+ @if ($filter['type'] == 'range')
+ (RANGE SLIDER)
+ {{-- Todo: handle range using Vue slider component: https://github.com/NightCatSama/vue-slider-component --}}
+ @endif
- {{-- <hr class="h-px bg-grey-200 my-4">--}}
- {{-- <h3 class="text-base mb-2">Filtre 02</h3>--}}
- {{-- @for ($i = 1; $i <= 5; $i++)--}}
- {{-- <div class="flex justify-between py-1 text-sm">--}}
- {{-- <label for="filter_2_{{ $i }}">--}}
- {{-- <input type="checkbox" id="filter_2_{{ $i }}" class="mr-2">--}}
- {{-- Option {{ chr($i + 64) }}--}}
- {{-- </label>--}}
- {{-- <div class="products-filters-count pl-8">({{ rand(10, 95) }})</div>--}}
- {{-- </div>--}}
- {{-- @endfor--}}
+ @if (!$loop->last)
+ <hr class="h-px bg-grey-200 my-4">
+ @endif
- {{-- </div>--}}
- {{-- </div>--}}
+ @endforeach
+
+ </div>
+ </div>
{{-- Main Products Grid Area --}}
<div class="products-grid flex-grow">
{{-- Grid summary header --}}
- {{-- <div class="products-grid-summary--}}
- {{-- sticky top-0 z-10 bg-grey-100 pt-4 pb-4 flex justify-between items-center--}}
- {{-- md:block">--}}
- {{-- <div class="products-grid-active-filters flex-grow text-sm">--}}
- {{-- <ul class="flex flex-wrap -mb-3 md:mb-0">--}}
- {{-- <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200 cursor-pointer">--}}
- {{-- Capteur de force - Galette--}}
- {{-- <img src="{{ asset('images/icon-close.svg') }}" alt="Remove" class="inline-block ml-3">--}}
- {{-- </li>--}}
- {{-- <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200 cursor-pointer">--}}
- {{-- Option 01--}}
- {{-- <img src="{{ asset('images/icon-close.svg') }}" alt="Remove" class="inline-block ml-3">--}}
- {{-- </li>--}}
- {{-- <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200 cursor-pointer">--}}
- {{-- Option C--}}
- {{-- <img src="{{ asset('images/icon-close.svg') }}" alt="Remove" class="inline-block ml-3">--}}
- {{-- </li>--}}
- {{-- <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200 cursor-pointer">--}}
- {{-- Option D--}}
- {{-- <img src="{{ asset('images/icon-close.svg') }}" alt="Remove" class="inline-block ml-3">--}}
- {{-- </li>--}}
- {{-- </ul>--}}
- {{-- </div>--}}
- {{-- <div class="products-grid-result-count pl-4 md:pl-0 font-display whitespace-no-wrap">12 Résultats</div>--}}
- {{-- </div>--}}
+ <div class="products-grid-summary
+ sticky top-0 z-10 bg-grey-100 pt-4 pb-4 flex justify-between items-center
+ md:block">
+ <div class="products-grid-active-filters flex-grow text-sm">
+ <ul class="flex flex-wrap -mb-3 md:mb-0">
+ <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200 cursor-pointer">
+ Capteur de force - Galette
+ <img src="{{ asset('images/icon-close.svg') }}" alt="Remove" class="inline-block ml-3">
+ </li>
+ <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200 cursor-pointer">
+ Option 01
+ <img src="{{ asset('images/icon-close.svg') }}" alt="Remove" class="inline-block ml-3">
+ </li>
+ <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200 cursor-pointer">
+ Option C
+ <img src="{{ asset('images/icon-close.svg') }}" alt="Remove" class="inline-block ml-3">
+ </li>
+ <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200 cursor-pointer">
+ Option D
+ <img src="{{ asset('images/icon-close.svg') }}" alt="Remove" class="inline-block ml-3">
+ </li>
+ </ul>
+ </div>
+ <div class="products-grid-result-count pl-4 md:pl-0 font-display whitespace-no-wrap">
+ {{ count($products) }}
+ @if(count($products) == 1)
+ {{ __('résultat') }}
+ @else
+ {{ __('résultats') }}
+ @endif
+ </div>
+ </div>
{{-- Product Grid --}}
<grid cols="auto" class="products-grid mt-6">