Преглед на файлове

Added filters for table

Alexander Musikhin преди 10 месеца
родител
ревизия
e3d6886008

+ 13 - 0
app/Helpers/DateHelper.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 namespace App\Helpers;
 
 use Carbon\Exceptions\InvalidDateException;
+use Exception;
 use Illuminate\Support\Carbon;
 use Illuminate\Support\Str;
 
@@ -64,5 +65,17 @@ class DateHelper
         return Carbon::parse($date)->addMonths($months)->isoFormat(self::DB_FORMAT);
     }
 
+    /**
+     * @param string $string
+     * @return bool
+     */
+    public static function isDate(string $string): bool
+    {
+        if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/",$string)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
 
 }

+ 0 - 37
app/Helpers/dateHelper.php

@@ -1,37 +0,0 @@
-<?php
-use Illuminate\Support\Carbon;
-
-if(!function_exists('humanDate')) {
-    function humanDate($date, $withTime = false): string
-    {
-        if(!$date) return '-';
-
-        $today = date('Y-m-d');
-        $tomorrow = date('Y-m-d', strtotime('tomorrow'));
-
-        $ret = '';
-
-        switch ($date) {
-            case $today:
-                $ret .= 'Сегодня';
-                break;
-            case $tomorrow:
-                $ret .= 'Завтра';
-                break;
-            default:
-                $ret .= Carbon::parse($date)->isoFormat('D MMMM');
-        }
-
-        $year = date('Y', strtotime($date));
-        if(date('Y') != $year){
-            $ret .= ' ' . $year;
-        }
-
-        if($withTime) {
-            $ret .= date(' H:i', strtotime($date));
-        }
-
-        return  $ret;
-    }
-}
-

+ 80 - 2
app/Http/Controllers/ProductController.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Controllers;
 
+use App\Helpers\DateHelper;
 use App\Jobs\Import\ImportCatalog;
 use App\Models\Product;
 use Illuminate\Http\RedirectResponse;
@@ -9,6 +10,7 @@ use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Str;
+use function PHPUnit\Framework\isString;
 
 class ProductController extends Controller
 {
@@ -19,8 +21,84 @@ class ProductController extends Controller
 
     public function index(Request $request)
     {
+        $filters = [
+            'type_tz' => [
+                'title'     => 'Тип по ТЗ',
+                'values'    => Product::getFilters('type_tz')
+            ],
+            'type' => [
+                'title'     => 'Тип',
+                'values'    => Product::getFilters('type')
+            ],
+        ];
+
+        $ranges = [
+            'product_price' => [
+                'title' => 'Цена товара',
+                'min'   => Product::query()->min('product_price') / 100,
+                'max'   => Product::query()->max('product_price') / 100,
+            ],
+            'installation_price' => [
+                'title' => 'Цена установки',
+                'min'   => Product::query()->min('installation_price') / 100,
+                'max'   => Product::query()->max('installation_price') / 100,
+            ],
+            'service_price' => [
+                'title' => 'Цена обслуживания',
+                'min'   => Product::query()->min('service_price') / 100,
+                'max'   => Product::query()->max('service_price') / 100,
+            ],
+            'total_price' => [
+                'title' => 'Итоговая цена',
+                'min'   => Product::query()->min('total_price') / 100,
+                'max'   => Product::query()->max('total_price') / 100,
+            ],
+        ];
+
+        $dates = [
+            'created_at' => [
+                'title' => 'Дата создания',
+                'min'   => DateHelper::getDateForDB(Product::query()->min('created_at')),
+                'max'   => DateHelper::getDateForDB(Product::query()->max('created_at')),
+            ]
+        ];
+
+
+        // fill filters
+        $this->data['filters'] = $filters;
+        $this->data['dates'] = $dates;
+        $this->data['ranges'] = $ranges;
+
+
+
+        // create request
         $q = Product::query();
 
+        // accept filters
+        if(!empty($request->filters) && is_array($request->filters)) {
+            foreach ($request->filters as $filterName => $filterValue) {
+                if(!$filterValue) continue;
+
+                if(Str::contains($filterName, 'price')) {
+                    $filterValue = $filterValue * 100;
+                }
+
+                if(Str::endsWith($filterName, '_from')) {
+                    if(isString($filterValue) && DateHelper::isDate($filterValue)) {
+                        $filterValue .= ' 00:00:00';
+                    }
+                    $q->where(Str::replace('_from', '', $filterName), '>=', $filterValue);
+                } elseif(Str::endsWith($filterName, '_to')) {
+                    if(isString($filterValue) && DateHelper::isDate($filterValue)) {
+                        $filterValue .= ' 23:59:59';
+                    }
+                    $q->where(Str::replace('_to', '', $filterName), '<=', $filterValue);
+                } else {
+                    $q->where($filterName, '=', $filterValue);
+                }
+            }
+        }
+
         // ------- setup sort and order --------------------------------------------------------------------------------
         $this->data['sortBy'] = (!empty($request->sortBy))
             ? Str::replace('_txt', '', $request->sortBy) // remove '_txt' fields modifier
@@ -28,14 +106,14 @@ class ProductController extends Controller
 
         // check for sortBy is valid field
         $p = new Product();
-        if(!in_array($this->data['sortBy'], $p->getFillable())) {
+        if(!in_array($this->data['sortBy'], array_merge(['id', 'created_at'], $p->getFillable()))) {
             $this->data['sortBy'] = Product::DEFAULT_SORT_BY;
         }
 
         // set order
         $this->data['orderBy'] = (!empty($request->order)) ? 'desc' : 'asc';
         $q->orderBy($this->data['sortBy'], $this->data['orderBy']);
-
+//        $q->dumpRawSql();
 
         $this->data['products'] = $q->paginate()->withQueryString();
         return view('catalog.index', $this->data);

+ 15 - 0
app/Models/Product.php

@@ -6,6 +6,7 @@ use App\Helpers\Price;
 use Illuminate\Database\Eloquent\Casts\Attribute;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Support\Facades\DB;
 
 class Product extends Model
 {
@@ -94,4 +95,18 @@ class Product extends Model
         );
     }
 
+    /**
+     * @param $column
+     * @return array
+     */
+    public static function getFilters($column): array
+    {
+        return DB::table('products')
+            ->select($column)
+            ->distinct()
+            ->get()
+            ->pluck($column)
+            ->toArray();
+    }
+
 }

+ 9 - 1
resources/sass/app.scss

@@ -28,7 +28,15 @@
   }
 }
 
-
+.table-buttons {
+  position: absolute;
+  right: 0;
+  opacity: 0.5;
+  z-index: 999;
+}
+.table-buttons:hover {
+  opacity: 1;
+}
 
 .table {
   tr th {

+ 1 - 0
resources/views/catalog/index.blade.php

@@ -33,6 +33,7 @@
             'service_price_txt'         => 'Цена обслуживания',
             'total_price_txt'           => 'Итоговая цена',
             'note'                      => 'Примечания',
+            'created_at'                => 'Дата создания',
         ],
         'data'  => $products
     ])

+ 3 - 0
resources/views/partials/select.blade.php

@@ -3,6 +3,9 @@
     <div class="@if(!($right ?? null)) col-md-8 @endif">
         <select name="{{ $name }}" id="{{ $name }}" class="form-select @error($name) is-invalid @enderror" >
             @foreach($options as $k => $v)
+                @php
+                    if(isset($key_as_val)) $k = $v;
+                @endphp
                 <option @selected($k == ($value ?? null)) value="{{ $k }}">{{ $v }}</option>
             @endforeach
         </select>

+ 110 - 4
resources/views/partials/table.blade.php

@@ -1,7 +1,17 @@
 
 <div class="table-responsive">
-    <div class="py-2 bg-primary rounded-start" style="position: absolute; right: 0">
-        <button type="button" class="btn btn-sm btn-primary " data-bs-toggle="modal" data-bs-target="#table_{{ $id }}_modal">
+
+
+    <div class="table-buttons py-2 bg-primary rounded-start d-flex flex-column ">
+        <button type="button" class="btn btn-sm text-white" data-bs-toggle="modal" data-bs-target="#table_{{ $id }}_modal_search">
+            <i class="bi bi-search"></i>
+        </button>
+
+        <button type="button" class="btn btn-sm text-white " data-bs-toggle="modal" data-bs-target="#table_{{ $id }}_modal_filters">
+            <i class="bi bi-funnel-fill"></i>
+        </button>
+
+        <button type="button" class="btn btn-sm text-white " data-bs-toggle="modal" data-bs-target="#table_{{ $id }}_modal_settings">
             <i class="bi bi-gear-fill"></i>
         </button>
     </div>
@@ -9,7 +19,7 @@
         <thead>
             <tr>
                 @foreach($header as $headerName => $headerTitle)
-                    <th scope="col" class="bg-primary-subtle column_{{ $headerName }} hrader_{{ $headerName }}">
+                    <th scope="col" class="bg-primary-subtle column_{{ $headerName }}">
                         <span class="cursor-pointer sort-by-column" data-name="{{ $headerName }}">
                             {{ $headerTitle }}
                             @if(str_starts_with($headerName, $sortBy))
@@ -20,6 +30,12 @@
                                 @endif
                             @endif
 
+                            @if(isset(request()->filters[$headerName]) ||
+                                isset(request()->filters[str_replace('_txt', '', $headerName) . '_from']) ||
+                                isset(request()->filters[str_replace('_txt', '', $headerName) . '_to'])
+                                )
+                                <i class="bi bi-funnel"></i>
+                            @endif
                         </span>
                     </th>
                 @endforeach
@@ -39,7 +55,7 @@
 </div>
 
 <!-- Модальное окно настроек таблицы -->
-<div class="modal fade" id="table_{{ $id }}_modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
+<div class="modal fade" id="table_{{ $id }}_modal_settings" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
     <div class="modal-dialog modal-fullscreen-sm-down">
         <div class="modal-content">
             <div class="modal-header">
@@ -60,6 +76,74 @@
     </div>
 </div>
 
+<!-- Модальное окно фильтров -->
+<div class="modal fade" id="table_{{ $id }}_modal_filters" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
+    <div class="modal-dialog modal-fullscreen-sm-down modal-lg">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h1 class="modal-title fs-5" id="exampleModalLabel">Фильтры по колонкам таблицы</h1>
+                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
+            </div>
+            <div class="modal-body">
+                <form class="filters">
+                    @foreach($filters as $filterName => $filter)
+                        @php array_unshift($filter['values'], '') @endphp
+                        @include('partials.select', [
+                                'name' => 'filters[' . $filterName . ']',
+                                'title' => $filter['title'],
+                                'options' => $filter['values'],
+                                'value' => request()->filters[$filterName] ?? '',
+                                'key_as_val' => true
+                            ])
+                    @endforeach
+                    @foreach($ranges as $rangeName => $range)
+
+                        @include('partials.input', [
+                                'name' => 'filters[' . $rangeName . '_from]',
+                                'type' => 'number',
+                                'title' => $range['title'] . ' с:',
+                                'min' => $range['min'],
+                                'max' => $range['max'],
+                                'value' => request()->filters[$rangeName . '_from'] ?? '', // $range['min']
+                            ])
+                        @include('partials.input', [
+                                'name' => 'filters[' . $rangeName . '_to]',
+                                'type' => 'number',
+                                'title' => ' по:',
+                                'min' => $range['min'],
+                                'max' => $range['max'],
+                                'value' => request()->filters[$rangeName . '_to'] ?? '', // $range['max']
+                            ])
+                    @endforeach
+                    @foreach($dates as $rangeName => $range)
+                        @include('partials.input', [
+                                'name' => 'filters[' . $rangeName . '_from]',
+                                'type' => 'date',
+                                'title' => $range['title'] . ' с:',
+                                'min' => $range['min'],
+                                'max' => $range['max'],
+                                'value' => request()->filters[$rangeName . '_from'] ?? '',
+                            ])
+                        @include('partials.input', [
+                                'name' => 'filters[' . $rangeName . '_to]',
+                                'type' => 'date',
+                                'title' => $range['title'] . ' по:',
+                                'min' => $range['min'],
+                                'max' => $range['max'],
+                                'value' => request()->filters[$rangeName . '_to'] ?? '',
+                            ])
+                    @endforeach
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-primary accept-filters" data-bs-dismiss="modal">Применить</button>
+                <button type="button" class="btn btn-outline-secondary reset-filters" data-bs-dismiss="modal">Сбросить</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+@dump($filters, $ranges, $dates)
 
 @push('scripts')
     <script type="module">
@@ -108,6 +192,28 @@
             document.location.href = currentUrl.href;
         });
 
+        $('.accept-filters').on('click', function () {
+            let filters = $('.filters').serializeArray();
+            let currentUrl = new URL(document.location.href);
+            $.each(filters, function (id, filter) {
+                if(filter.value !== '') {
+                    currentUrl.searchParams.set(filter.name, filter.value);
+                } else {
+                    currentUrl.searchParams.delete(filter.name);
+                }
+            });
+            document.location.href = currentUrl.href;
+        });
+
+        $('.reset-filters').on('click', function () {
+            let filters = $('.filters').serializeArray();
+            let currentUrl = new URL(document.location.href);
+            $.each(filters, function (id, filter) {
+                currentUrl.searchParams.delete(filter.name);
+            });
+            document.location.href = currentUrl.href;
+        });
+
 
 
     </script>

+ 1 - 1
todo.md

@@ -2,7 +2,7 @@
 - [x] управление пользователями (CRUD)
 - [x] каталог товаров
 - [x] настраиваемые таблицы
-- [ ] каталог товаров: фильтры, поиск, +сортировка
+- [ ] каталог товаров: +фильтры, поиск, +сортировка
 - [ ] каталог товаров: экспорт
 - [x] каталог товаров: импорт
 - [ ] складские остатки товаров