[], 'ranges' => [], 'dates' => [], ]; /** * @param Model $model * @param string ...$columnNames * @return void */ protected function createDateFilters(Model $model, string ...$columnNames): void { foreach ($columnNames as $columnName) { $this->data['dates'][$columnName] = [ 'title' => $this->data['header'][$columnName], 'min' => DateHelper::getDateForDB($model::query()->min($columnName) ?? ''), 'max' => DateHelper::getDateForDB($model::query()->max($columnName) ?? ''), ]; } } /** * @param Model $model * @param array $columnNames * @return void */ protected function createRangeFilters(Model $model, string ...$columnNames): void { foreach ($columnNames as $columnName) { if(str_ends_with($columnName, '_price')) { $this->data['ranges'][$columnName] = [ 'title' => $this->data['header'][$columnName], 'min' => $model::query()->min($columnName) / 100, 'max' => $model::query()->max($columnName) / 100, ]; } else { $this->data['ranges'][$columnName] = [ 'title' => $this->data['header'][$columnName ], 'min' => $model::query()->min($columnName), 'max' => $model::query()->max($columnName), ]; } } } /** * @param Model $model * @param array $columns * @return void */ protected function createFilters(Model $model, string ...$columns): void { foreach ($columns as $column) { $uniqueValues = $model::query()->distinct()->get($column)->pluck($column)->toArray(); foreach ($uniqueValues as $k => $v) { if(!$v) { $uniqueValues[$k] = '-пусто-'; } } $result = []; foreach ($uniqueValues as $val){ if(str_ends_with($column, '_id')) { $relation = Str::camel(str_replace('_id', '', $column)); $result[$val] = $model::query()->where($column, '=', $val)->first()?->$relation->name; } else { $result[$val] = $val; } } $this->data['filters'][$column] = [ 'title' => $this->data['header'][$column], 'values' => $result ]; } } /** * @param Model $model * @param Request $request * @return void */ protected function setSortAndOrderBy(Model $model, Request $request): void { // ------- setup sort and order -------------------------------------------------------------------------------- $this->data['sortBy'] = (!empty($request->sortBy)) ? Str::replace('_txt', '', $request->sortBy) // remove '_txt' fields modifier : $model::DEFAULT_SORT_BY ?? 'created_at'; // check for sortBy is valid field $p = new $model(); if(!in_array($this->data['sortBy'], array_merge(['id', 'created_at'], $p->getFillable()))) { $this->data['sortBy'] = $model::DEFAULT_SORT_BY ?? 'created_at'; } if(!empty($request->per_page)) { $this->data['per_page'] = $request->per_page; } else { $this->data['per_page'] = config('pagination.per_page'); } session(['per_page' => $request->per_page]); // set order $this->data['orderBy'] = (empty($request->order)) ? 'asc' : 'desc'; } /** * @param Builder $query * @param Request $request * @return void */ protected function acceptFilters(Builder $query, Request $request): void { // 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 && $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($dbValue == '-пусто-') { $query->whereNull($dbColumn); } else { $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 * @return void */ protected function acceptSearch(Builder $query, Request $request): void { // accept search if(!empty($request->s)) { $s = $request->s; $searchFields = $this->data['searchFields']; $query->where(function ($query) use ($searchFields, $s) { foreach ($searchFields as $searchField) { if(Str::contains($searchField, '-')) { list($relation, $column) = explode('-', $searchField); $query->orWhereHas($relation, function ($query) use ($s, $column) { $query->where($column, 'LIKE', "%{$s}%"); }); } else { $query->orWhere($searchField, 'LIKE', '%' . $s . '%'); } } }); } } }