Jelajahi Sumber

fix filters

Alexander Musikhin 3 minggu lalu
induk
melakukan
7014bb8066

+ 6 - 25
SPARE_PARTS_MODULE.md

@@ -110,39 +110,20 @@
 - Модель `SparePart` **НЕ использует** `YearScope`
 - Одна запчасть видна во всех годах
 
-### Заказы деталей (С year):
-- Таблица `spare_part_orders` **имеет** поле `year`
-- Модель `SparePartOrder` **использует** `YearScope`
-- Заказы разделены по годам
+### Заказы деталей (БЕЗ year):
+- Таблица `spare_part_orders` **НЕ имеет** поле `year`
+- Модель `SparePartOrder` **НЕ использует** `YearScope`
+- Заказы не разделяются по годам
 
 ### Логика вычисления количеств:
 - Вычисляемые поля (`quantity_without_docs`, `quantity_with_docs`) в модели `SparePart`
-- Учитывают **только заказы текущего года** из сессии `year()`
-- При смене года количества пересчитываются автоматически
-
-**Пример:**
-```php
-// Запчасть "BOLT-001" существует одна
-$sparePart = SparePart::where('article', 'BOLT-001')->first();
-
-// Год 2024: есть заказ на 100 шт
-session(['year' => 2024]);
-echo $sparePart->total_quantity; // 100
-
-// Год 2025: есть заказ на 50 шт
-session(['year' => 2025]);
-echo $sparePart->total_quantity; // 50
-
-// Год 2026: нет заказов
-session(['year' => 2026]);
-echo $sparePart->total_quantity; // 0
-```
+- Учитывают все заказы независимо от года
 
 ## Структура базы данных
 
 ### Таблицы:
 1. `spare_parts` - каталог запчастей (БЕЗ year)
-2. `spare_part_orders` - заказы деталей (С year)
+2. `spare_part_orders` - заказы деталей (БЕЗ year)
 3. `spare_part_order_shipments` - история списаний (БЕЗ year, привязана к заказу)
 4. `pricing_codes` - справочник расценок
 5. `reclamation_spare_part` - pivot таблица запчасти-рекламации

+ 80 - 11
app/Http/Controllers/Controller.php

@@ -133,27 +133,96 @@ class Controller extends BaseController
     {
         // accept filters
         if(!empty($request->filters) && is_array($request->filters)) {
+
+            // Собираем имена range и date фильтров для пропуска _from/_to в основном цикле
+            $rangeKeys = array_keys($this->data['ranges'] ?? []);
+            $dateKeys = array_keys($this->data['dates'] ?? []);
+            $rangeAndDateKeys = array_merge($rangeKeys, $dateKeys);
+
             foreach ($request->filters as $filterName => $filterValue) {
-                if(!$filterValue) continue;
-                if(Str::contains($filterValue, '||')) {
-                    $values = explode('||', $filterValue);
-                    foreach ($values as $v) {
-                        if($v == '-пусто-')
-                            $query->whereNull($filterName);
-                    }
-                    $query->orWhereIn($filterName, $values);
+                if(!$filterValue && $filterValue !== '0') continue;
+
+                // Пропускаем _from/_to — они обрабатываются ниже
+                if(Str::endsWith($filterName, ['_from', '_to'])) {
+                    continue;
+                }
+
+                // Резолвим виртуальные столбцы и значения
+                [$dbColumn, $dbValue] = $this->resolveFilterColumn($filterName, $filterValue);
+
+                if(Str::contains($dbValue, '||')) {
+                    $values = explode('||', $dbValue);
+                    $query->where(function ($q) use ($dbColumn, $values) {
+                        $nonNullValues = [];
+                        foreach ($values as $v) {
+                            if ($v == '-пусто-') {
+                                $q->orWhereNull($dbColumn);
+                            } else {
+                                $nonNullValues[] = $v;
+                            }
+                        }
+                        if (!empty($nonNullValues)) {
+                            $q->orWhereIn($dbColumn, $nonNullValues);
+                        }
+                    });
                 } else {
-                    if($filterValue == '-пусто-') {
-                        $query->whereNull($filterName);
+                    if($dbValue == '-пусто-') {
+                        $query->whereNull($dbColumn);
                     } else {
-                        $query->where($filterName, $filterValue);
+                        $query->where($dbColumn, $dbValue);
                     }
+                }
+            }
+
+            // Обработка range-фильтров (_from / _to)
+            foreach ($rangeAndDateKeys as $columnName) {
+                $fromValue = $request->filters[$columnName . '_from'] ?? null;
+                $toValue = $request->filters[$columnName . '_to'] ?? null;
 
+                // Для price-полей значения в форме в рублях, в БД в копейках
+                $multiplier = str_ends_with($columnName, '_price') ? 100 : 1;
+
+                if($fromValue !== null && $fromValue !== '') {
+                    $query->where($columnName, '>=', $fromValue * $multiplier);
+                }
+                if($toValue !== null && $toValue !== '') {
+                    $query->where($columnName, '<=', $toValue * $multiplier);
                 }
             }
         }
     }
 
+    /**
+     * Резолвит виртуальные имена столбцов и маппит отображаемые значения обратно на значения БД.
+     *
+     * @return array{0: string, 1: string} [реальный_столбец, значение_для_бд]
+     */
+    protected function resolveFilterColumn(string $filterName, string $filterValue): array
+    {
+        $table = $this->data['id'] ?? '';
+        $columnMap = FilterController::COLUMN_MAP[$table] ?? [];
+        $valueMap = FilterController::VALUE_MAP[$table] ?? [];
+
+        // Резолвим имя столбца: явный маппинг → убираем _txt
+        if (isset($columnMap[$filterName])) {
+            $dbColumn = $columnMap[$filterName];
+        } elseif (str_ends_with($filterName, '_txt')) {
+            $dbColumn = substr($filterName, 0, -4);
+        } else {
+            $dbColumn = $filterName;
+        }
+
+        // Обратный маппинг значений
+        if (isset($valueMap[$dbColumn])) {
+            $reverseMap = array_flip($valueMap[$dbColumn]);
+            $parts = explode('||', $filterValue);
+            $mapped = array_map(fn($v) => (string)($reverseMap[$v] ?? $v), $parts);
+            $filterValue = implode('||', $mapped);
+        }
+
+        return [$dbColumn, $filterValue];
+    }
+
     /**
      * @param Builder $query
      * @param Request $request

+ 77 - 10
app/Http/Controllers/FilterController.php

@@ -21,6 +21,40 @@ class FilterController extends Controller
         'spare_parts'   => 'spare_parts',
         'spare_part_orders' => 'spare_part_orders_view',
     ];
+
+    const SKIP_YEAR_FILTER = [
+        'reclamations',
+    ];
+
+    /**
+     * Маппинг виртуальных столбцов (аксессоров Eloquent) на реальные столбцы БД.
+     * Ключ — имя таблицы из DB_TABLES, значение — массив 'виртуальный_столбец' => 'реальный_столбец'.
+     */
+    const COLUMN_MAP = [
+        'spare_part_orders' => [
+            'status_name'         => 'status',
+            'with_documents_text' => 'with_documents',
+        ],
+    ];
+
+    /**
+     * Маппинг значений: реальное значение БД => отображаемое значение.
+     * Ключ — имя таблицы, затем реальный столбец.
+     */
+    const VALUE_MAP = [
+        'spare_part_orders' => [
+            'with_documents' => [
+                0 => 'Нет',
+                1 => 'Да',
+            ],
+            'status' => [
+                'ordered'  => 'Заказано',
+                'in_stock' => 'На складе',
+                'shipped'  => 'Отгружено',
+            ],
+        ],
+    ];
+
     public function getFilters(FilterRequest $request)
     {
         $table = $request->validated('table');
@@ -30,13 +64,15 @@ class FilterController extends Controller
         }
         $gp = session('gp_' . $table);
 
-
         $dbTable = self::DB_TABLES[$table];
 
-        if (Schema::hasColumn($dbTable, $column)) {
-            $q = DB::table($dbTable)->select($column)->distinct();
-            if (Schema::hasColumn($dbTable, 'year')) {
-                    $q->where('year' , year());
+        // Определяем реальный столбец БД
+        $dbColumn = self::resolveDbColumn($table, $dbTable, $column);
+
+        if ($dbColumn && Schema::hasColumn($dbTable, $dbColumn)) {
+            $q = DB::table($dbTable)->select($dbColumn)->distinct();
+            if (!in_array($table, self::SKIP_YEAR_FILTER) && Schema::hasColumn($dbTable, 'year')) {
+                $q->where('year' , year());
             }
             if (Schema::hasColumn($dbTable, 'deleted_at')) {
                 $q->whereNull('deleted_at');
@@ -44,20 +80,51 @@ class FilterController extends Controller
 
             if(isset($gp['filters']) && is_array($gp['filters']) && count($gp['filters'])) {
                 foreach ($gp['filters'] as $colName => $vals) {
-                    $q->where(function ($query) use ($gp, $colName, $vals) {
+                    if ($colName === $column) continue;
+                    $filterDbColumn = self::resolveDbColumn($table, $dbTable, $colName);
+                    if (!$filterDbColumn || !Schema::hasColumn($dbTable, $filterDbColumn)) continue;
+                    $q->where(function ($query) use ($filterDbColumn, $vals) {
                         foreach (explode('||', $vals) as $val) {
-                            if($val == '-пусто-') $val = null;
-                            $query->orWhere($colName, '=', $val);
+                            if($val == '-пусто-') {
+                                $query->orWhereNull($filterDbColumn);
+                            } else {
+                                $query->orWhere($filterDbColumn, '=', $val);
+                            }
                         }
                     });
                 }
             }
-//            dd($q->toRawSql());
-            $result = $q->orderBy($column)->get()->pluck($column)->toArray();
+
+            $result = $q->orderBy($dbColumn)->get()->pluck($dbColumn)->toArray();
+
+            // Применяем маппинг значений, если есть
+            if (isset(self::VALUE_MAP[$table][$dbColumn])) {
+                $map = self::VALUE_MAP[$table][$dbColumn];
+                $result = array_map(fn($val) => $map[$val] ?? $val, $result);
+            }
         } else {
             $result = [];
         }
 
         return response()->json($result, 200, [], JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
     }
+
+    /**
+     * Определяет реальный столбец БД по имени столбца из заголовка.
+     * Приоритет: прямое совпадение в БД → COLUMN_MAP для конкретной таблицы.
+     */
+    public static function resolveDbColumn(string $table, string $dbTable, string $column): ?string
+    {
+        // 1. Прямое совпадение — столбец существует в БД
+        if (Schema::hasColumn($dbTable, $column)) {
+            return $column;
+        }
+
+        // 2. Явный маппинг для конкретной таблицы
+        if (isset(self::COLUMN_MAP[$table][$column])) {
+            return self::COLUMN_MAP[$table][$column];
+        }
+
+        return null;
+    }
 }

+ 4 - 4
app/Http/Controllers/SparePartController.php

@@ -64,10 +64,10 @@ class SparePartController extends Controller
 
         // Для range фильтров нужно использовать реальные поля БД (без _txt)
         // но заголовки брать из header с _txt
-        $this->createRangeFiltersForPrices($model, 'customer_price', 'expertise_price', 'min_stock');
-        if (hasRole('admin')) {
-            $this->createRangeFiltersForPrices($model, 'purchase_price');
-        }
+//        $this->createRangeFiltersForPrices($model, 'customer_price', 'expertise_price', 'min_stock');
+//        if (hasRole('admin')) {
+//            $this->createRangeFiltersForPrices($model, 'purchase_price');
+//        }
 
         // Запрос
         $q = $model::query()->with('pricingCodes');

+ 0 - 18
app/Http/Controllers/SparePartOrderController.php

@@ -81,24 +81,6 @@ class SparePartOrderController extends Controller
             }
         }
 
-        if ($request->has('with_documents')) {
-            $withDocsValue = $request->get('with_documents');
-            // Преобразуем значение в boolean
-            if ($withDocsValue === 'Да' || $withDocsValue === '1') {
-                $q->where('with_documents', true);
-            } elseif ($withDocsValue === 'Нет' || $withDocsValue === '0') {
-                $q->where('with_documents', false);
-            }
-        }
-
-        if ($request->has('status')) {
-            $q->where('status', $request->get('status'));
-        }
-
-        if ($request->has('available_qty_min')) {
-            $q->where('available_qty', '>=', $request->get('available_qty_min'));
-        }
-
         $this->acceptFilters($q, $request);
         $this->acceptSearch($q, $request);
 

+ 0 - 1
app/Models/SparePart.php

@@ -171,7 +171,6 @@ class SparePart extends Model
     }
 
     // ========== ВЫЧИСЛЯЕМЫЕ ПОЛЯ ОСТАТКОВ ==========
-    // Примечание: YearScope автоматически применяется к SparePartOrder
 
     /**
      * Физический остаток БЕЗ документов

+ 0 - 1
database/migrations/2026_01_24_200006_recreate_spare_part_orders_view.php

@@ -18,7 +18,6 @@ return new class extends Migration
             CREATE VIEW spare_part_orders_view AS
             SELECT
                 spo.id,
-                spo.year,
                 spo.spare_part_id,
                 spo.source_text,
                 spo.sourceable_id,

+ 0 - 2
database/migrations/2026_02_02_230356_add_order_number_to_spare_part_orders.php

@@ -24,7 +24,6 @@ return new class extends Migration
             CREATE VIEW spare_part_orders_view AS
             SELECT
                 spo.id,
-                spo.year,
                 spo.spare_part_id,
                 spo.order_number,
                 spo.source_text,
@@ -60,7 +59,6 @@ return new class extends Migration
             CREATE VIEW spare_part_orders_view AS
             SELECT
                 spo.id,
-                spo.year,
                 spo.spare_part_id,
                 spo.source_text,
                 spo.sourceable_id,

+ 90 - 0
database/migrations/2026_02_12_120001_remove_year_from_spare_part_orders.php

@@ -0,0 +1,90 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        DB::statement("DROP VIEW IF EXISTS spare_part_orders_view");
+
+        if (Schema::hasColumn('spare_part_orders', 'year')) {
+            Schema::table('spare_part_orders', function (Blueprint $table) {
+                $table->dropColumn('year');
+            });
+        }
+
+        DB::statement("
+            CREATE VIEW spare_part_orders_view AS
+            SELECT
+                spo.id,
+                spo.spare_part_id,
+                spo.order_number,
+                spo.source_text,
+                spo.sourceable_id,
+                spo.sourceable_type,
+                spo.status,
+                spo.ordered_quantity,
+                spo.available_qty,
+                spo.with_documents,
+                spo.note,
+                spo.user_id,
+                spo.deleted_at,
+                spo.created_at,
+                spo.updated_at,
+                sp.article,
+                u.name as user_name
+            FROM spare_part_orders spo
+            LEFT JOIN spare_parts sp ON sp.id = spo.spare_part_id
+            LEFT JOIN users u ON u.id = spo.user_id
+            WHERE spo.deleted_at IS NULL
+        ");
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        DB::statement("DROP VIEW IF EXISTS spare_part_orders_view");
+
+        if (!Schema::hasColumn('spare_part_orders', 'year')) {
+            Schema::table('spare_part_orders', function (Blueprint $table) {
+                $table->unsignedInteger('year')->nullable()->after('id');
+            });
+        }
+
+        DB::statement("
+            CREATE VIEW spare_part_orders_view AS
+            SELECT
+                spo.id,
+                spo.year,
+                spo.spare_part_id,
+                spo.order_number,
+                spo.source_text,
+                spo.sourceable_id,
+                spo.sourceable_type,
+                spo.status,
+                spo.ordered_quantity,
+                spo.available_qty,
+                spo.with_documents,
+                spo.note,
+                spo.user_id,
+                spo.deleted_at,
+                spo.created_at,
+                spo.updated_at,
+                sp.article,
+                u.name as user_name
+            FROM spare_part_orders spo
+            LEFT JOIN spare_parts sp ON sp.id = spo.spare_part_id
+            LEFT JOIN users u ON u.id = spo.user_id
+            WHERE spo.deleted_at IS NULL
+        ");
+    }
+};

+ 155 - 99
resources/views/partials/newFilterElement.blade.php

@@ -7,20 +7,46 @@
                 <button type="button" class="btn btn-outline-secondary btn-sm w-100" id="sort-by-desc-{{$id}}"><i class="bi bi-arrow-down"></i>DESC</button>
             </div>
         </div>
-        <form class="dropdown-filter_{{$id}} mb-2">
-            <div class="d-flex align-items-center mb-2">
-                <input class="form-control me-2" id="search_{{$id}}" placeholder="Поиск">
-                <button type="button" class="btn btn-outline-secondary btn-sm" id="sort-filter-{{$id}}" title="А-Я / Я-А">
-                    <i class="bi bi-sort-alpha-down" id="sort-filter-icon-{{$id}}"></i>
-                </button>
+        @if($type === 'ranges')
+            @php
+                $inputType = $type === 'dates' ? 'date' : 'number';
+                $fromKey = $id . '_from';
+                $toKey = $id . '_to';
+            @endphp
+            <form class="dropdown-filter_{{$id}} mb-2">
+                <div class="mb-2">
+                    <label class="form-label mb-0 small">От:</label>
+                    <input type="{{ $inputType }}" class="form-control form-control-sm" id="range_from_{{$id}}"
+                           value="{{ request()->filters[$fromKey] ?? '' }}"
+                           @if($data) min="{{ $data['min'] }}" max="{{ $data['max'] }}" @endif>
+                </div>
+                <div class="mb-2">
+                    <label class="form-label mb-0 small">До:</label>
+                    <input type="{{ $inputType }}" class="form-control form-control-sm" id="range_to_{{$id}}"
+                           value="{{ request()->filters[$toKey] ?? '' }}"
+                           @if($data) min="{{ $data['min'] }}" max="{{ $data['max'] }}" @endif>
+                </div>
+            </form>
+            <div class="modal-footer d-flex justify-content-between" id="modal-footer_{{$id}}">
+                <button type="button" class="btn btn-outline-secondary btn-sm w-50 border-0" id="reset-filters_{{$id}}">Отмена</button>
+                <button type="button" class="btn btn-primary btn-sm w-50" id="accept-filter_{{$id}}">Применить</button>
             </div>
-            <div class="ps-1" id="filter-list-{{$id}}" style="max-height: 200px; overflow-y: scroll;">
+        @else
+            <form class="dropdown-filter_{{$id}} mb-2">
+                <div class="d-flex align-items-center mb-2">
+                    <input class="form-control me-2" id="search_{{$id}}" placeholder="Поиск">
+                    <button type="button" class="btn btn-outline-secondary btn-sm" id="sort-filter-{{$id}}" title="А-Я / Я-А">
+                        <i class="bi bi-sort-alpha-down" id="sort-filter-icon-{{$id}}"></i>
+                    </button>
+                </div>
+                <div class="ps-1" id="filter-list-{{$id}}" style="max-height: 200px; overflow-y: scroll;">
+                </div>
+            </form>
+            <div class="modal-footer d-flex justify-content-between" id="modal-footer_{{$id}}">
+                <button type="button" class="btn btn-outline-secondary reset-filters_{{$id}} btn-sm w-50 border-0" id="reset-filters_{{$id}}">Отмена</button>
+                <button type="button" class="btn btn-primary accept-filter_{{$id}} btn-sm w-50" id="accept-filter_{{$id}}">Применить</button>
             </div>
-        </form>
-        <div class="modal-footer d-flex justify-content-between" id="modal-footer_{{$id}}">
-            <button type="button" class="btn btn-outline-secondary reset-filters_{{$id}} btn-sm w-50 border-0" id="reset-filters_{{$id}}">Отмена</button>
-            <button type="button" class="btn btn-primary accept-filter_{{$id}} btn-sm w-50" id="accept-filter_{{$id}}">Применить</button>
-        </div>
+        @endif
     </div>
 </div>
 
@@ -44,109 +70,139 @@
         }
 
         waitForJQuery(async function () {
-            const $container = $("#filter-list-{{$id}}");
-            const $searchInput = $("#search_{{$id}}");
-            const $sortBtn = $("#sort-filter-{{$id}}");
-            const $sortIcon = $("#sort-filter-icon-{{$id}}");
-            const urlParams = new URL(document.location.href);
-            const existingFilter = urlParams.searchParams.get(`filters[{{$id}}]`);
-            const selectedValues = existingFilter ? existingFilter.split("||") : [];
-
-            let filterData = [];
-            let sortAsc = true;
-
-            function renderFilterList(data) {
-                const html = data.map(item => `
-                    <div class="form-check">
-                        <input class="form-check-input" type="checkbox" value="${escapeHtml(item)}" id="flexCheckDefault_{{$id}}_${escapeHtml(item)}">
-                        <label class="form-check-label" for="flexCheckDefault_{{$id}}_${escapeHtml(item)}">
-                            ${escapeHtml(item)}
-                        </label>
-                    </div>
-                `).join('');
-                $container.html(html);
-
-                selectedValues.forEach(value => {
-                    $container.find(`.form-check-input[value="${$.escapeSelector(value)}"]`).prop("checked", true);
+            @if($type === 'ranges')
+                $("#accept-filter_{{$id}}").on("click", function () {
+                    let currentUrl = new URL(document.location.href);
+                    const fromVal = $("#range_from_{{$id}}").val();
+                    const toVal = $("#range_to_{{$id}}").val();
+
+                    if (fromVal) {
+                        currentUrl.searchParams.set('filters[{{$id}}_from]', fromVal);
+                    } else {
+                        currentUrl.searchParams.delete('filters[{{$id}}_from]');
+                    }
+                    if (toVal) {
+                        currentUrl.searchParams.set('filters[{{$id}}_to]', toVal);
+                    } else {
+                        currentUrl.searchParams.delete('filters[{{$id}}_to]');
+                    }
+
+                    currentUrl.searchParams.delete('page');
+                    document.location.href = currentUrl.href;
                 });
-            }
 
-            function sortData(asc) {
-                const collator = new Intl.Collator('ru', { sensitivity: 'base', numeric: true });
-                return [...filterData].sort((a, b) => {
-                    if (a === '-пусто-') return -1;
-                    if (b === '-пусто-') return 1;
-                    return asc ? collator.compare(a, b) : collator.compare(b, a);
+                $("#reset-filters_{{$id}}").on("click", function () {
+                    let currentUrl = new URL(document.location.href);
+                    currentUrl.searchParams.delete('filters[{{$id}}_from]');
+                    currentUrl.searchParams.delete('filters[{{$id}}_to]');
+                    currentUrl.searchParams.delete('page');
+                    document.location.href = currentUrl.href;
                 });
-            }
+            @else
+                const $container = $("#filter-list-{{$id}}");
+                const $searchInput = $("#search_{{$id}}");
+                const $sortBtn = $("#sort-filter-{{$id}}");
+                const $sortIcon = $("#sort-filter-icon-{{$id}}");
+                const urlParams = new URL(document.location.href);
+                const existingFilter = urlParams.searchParams.get(`filters[{{$id}}]`);
+                const selectedValues = existingFilter ? existingFilter.split("||") : [];
+
+                let filterData = [];
+                let sortAsc = true;
+
+                function renderFilterList(data) {
+                    const html = data.map(item => `
+                        <div class="form-check">
+                            <input class="form-check-input" type="checkbox" value="${escapeHtml(item)}" id="flexCheckDefault_{{$id}}_${escapeHtml(item)}">
+                            <label class="form-check-label" for="flexCheckDefault_{{$id}}_${escapeHtml(item)}">
+                                ${escapeHtml(item)}
+                            </label>
+                        </div>
+                    `).join('');
+                    $container.html(html);
+
+                    selectedValues.forEach(value => {
+                        $container.find(`.form-check-input[value="${$.escapeSelector(value)}"]`).prop("checked", true);
+                    });
+                }
 
-            try {
-                const response = await fetch(`{!! route('getFilters', ['column' => $id, 'table' => $table]) !!}`);
-                const data = await response.json();
+                function sortData(asc) {
+                    const collator = new Intl.Collator('ru', { sensitivity: 'base', numeric: true });
+                    return [...filterData].sort((a, b) => {
+                        if (a === '-пусто-') return -1;
+                        if (b === '-пусто-') return 1;
+                        return asc ? collator.compare(a, b) : collator.compare(b, a);
+                    });
+                }
+
+                try {
+                    const response = await fetch(`{!! route('getFilters', ['column' => $id, 'table' => $table]) !!}`);
+                    const data = await response.json();
+
+                    if (Array.isArray(data) && data.length) {
+                        if(data[0] === null) data[0] = '-пусто-';
+                        filterData = data;
+                        const sortedData = sortData(sortAsc);
+                        renderFilterList(sortedData);
+                    } else {
+                        $("#search_{{$id}}").parent().hide();
+                        $("#modal-footer_{{$id}}").hide();
+                        $container.html('<div class="text-muted">Нет данных</div>');
+                    }
 
-                if (Array.isArray(data) && data.length) {
-                    if(data[0] === null) data[0] = '-пусто-';
-                    filterData = data;
+                } catch (error) {
+                    console.error("Ошибка при загрузке фильтров:", error);
+                    $container.html('<div class="text-danger">Ошибка загрузки</div>');
+                }
+
+                $sortBtn.on("click", function (e) {
+                    e.preventDefault();
+                    sortAsc = !sortAsc;
+                    $sortIcon.removeClass("bi-sort-alpha-down bi-sort-alpha-up")
+                        .addClass(sortAsc ? "bi-sort-alpha-down" : "bi-sort-alpha-up");
                     const sortedData = sortData(sortAsc);
                     renderFilterList(sortedData);
-                } else {
-                    $("#search_{{$id}}").parent().hide();
-                    $("#modal-footer_{{$id}}").hide();
-                    $container.html('<div class="text-muted">Нет данных</div>');
-                }
+                });
 
-            } catch (error) {
-                console.error("Ошибка при загрузке фильтров:", error);
-                $container.html('<div class="text-danger">Ошибка загрузки</div>');
-            }
+                $("#accept-filter_{{$id}}").on("click", function () {
+                    const checkedValues = $container.find(".form-check-input:checked")
+                        .map(function () {
+                            return $(this).val();
+                        })
+                        .get();
 
-            $sortBtn.on("click", function (e) {
-                e.preventDefault();
-                sortAsc = !sortAsc;
-                $sortIcon.removeClass("bi-sort-alpha-down bi-sort-alpha-up")
-                    .addClass(sortAsc ? "bi-sort-alpha-down" : "bi-sort-alpha-up");
-                const sortedData = sortData(sortAsc);
-                renderFilterList(sortedData);
-            });
+                    let currentUrl = new URL(document.location.href);
+                    if(!checkedValues.join('||')) {
+                        currentUrl.searchParams.delete('filters[{{$id}}]');
+                    } else {
+                        currentUrl.searchParams.set('filters[{{$id}}]', checkedValues.join('||'));
+                    }
 
-            $("#accept-filter_{{$id}}").on("click", function () {
-                const checkedValues = $container.find(".form-check-input:checked")
-                    .map(function () {
-                        return $(this).val();
-                    })
-                    .get();
+                    currentUrl.searchParams.delete('page');
+                    document.location.href = currentUrl.href;
+                });
 
-                let currentUrl = new URL(document.location.href);
-                if(!checkedValues.join('||')) {
+                $("#reset-filters_{{$id}}").on("click", function () {
+                    let currentUrl = new URL(document.location.href);
                     currentUrl.searchParams.delete('filters[{{$id}}]');
-                } else {
-                    currentUrl.searchParams.set('filters[{{$id}}]', checkedValues.join('||'));
-                }
-
-                currentUrl.searchParams.delete('page');
-                document.location.href = currentUrl.href;
-            });
-
-            $("#reset-filters_{{$id}}").on("click", function () {
-                let currentUrl = new URL(document.location.href);
-                currentUrl.searchParams.delete('filters[{{$id}}]');
-                document.location.href = currentUrl.href;
-            });
+                    document.location.href = currentUrl.href;
+                });
 
-            $searchInput.on("input", function () {
-                const query = $(this).val().toLowerCase();
+                $searchInput.on("input", function () {
+                    const query = $(this).val().toLowerCase();
 
-                $container.find(".form-check").each(function () {
-                    const labelText = $(this).find("label").text().toLowerCase();
+                    $container.find(".form-check").each(function () {
+                        const labelText = $(this).find("label").text().toLowerCase();
 
-                    if (labelText.includes(query)) {
-                        $(this).show();
-                    } else {
-                        $(this).hide();
-                        $(this).find(".form-check-input").prop("checked", false);
-                    }
+                        if (labelText.includes(query)) {
+                            $(this).show();
+                        } else {
+                            $(this).hide();
+                            $(this).find(".form-check-input").prop("checked", false);
+                        }
+                    });
                 });
-            });
+            @endif
 
             $('#sort-by-asc-{{$id}}').on('click', function () {
                 let columnName = '{{$id}}';

+ 14 - 15
resources/views/partials/table.blade.php

@@ -37,7 +37,19 @@
                             @endif
                         </div>
                         @if($headerName !== 'image')
-{{--                        @if(isset((array_merge($filters, $ranges, $dates))[$headerName]) || ($headerName == $sortBy))--}}
+                            @php
+                                $type = null;
+                                $data = (array_merge($filters, $ranges, $dates))[$headerName] ?? null;
+
+                                if (isset($filters[$headerName])) {
+                                    $type = 'filters';
+                                } elseif (isset($ranges[$headerName])) {
+                                    // Для dropdown фильтров вместо диапазона используем список всех вариантов
+                                    $type = 'filters';
+                                } elseif (isset($dates[$headerName])) {
+                                    $type = 'dates';
+                                }
+                            @endphp
                             <div class="text-end cursor-pointer dropdown" data-bs-auto-close="outside" aria-expanded="false">
                                 <i
                                         data-bs-auto-close="outside"
@@ -53,19 +65,6 @@
                                     bi-funnel
                                 @endif
                                 " id="{{$headerName}}"></i>
-                                @php
-                                    $mergedData = array_merge($filters, $ranges, $dates);
-                                    $type = null;
-                                    $data = (array_merge($filters, $ranges, $dates))[$headerName] ?? null;
-
-                                    if (isset($filters[$headerName])) {
-                                        $type = 'filters';
-                                    } elseif (isset($ranges[$headerName])) {
-                                        $type = 'ranges';
-                                    } elseif (isset($dates[$headerName])) {
-                                        $type = 'dates';
-                                    }
-                                @endphp
                                 @include('partials.newFilterElement', ['id' => $headerName, 'data' => $data, 'type' => $type, 'table' => $id, 'isSort' => $headerName == $sortBy, '$orderBy' => $orderBy])
                             </div>
                         @endif
@@ -463,4 +462,4 @@
         });
         }); // end waitForJQuery
     </script>
-@endpush
+@endpush