first commit
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use App\Models\OrganizationModel;
|
||||
|
||||
/**
|
||||
* BaseController provides a convenient place for loading components
|
||||
* and performing functions that are needed by all your controllers.
|
||||
*
|
||||
* Extend this class in any new controllers:
|
||||
* ```
|
||||
* class Home extends BaseController
|
||||
* ```
|
||||
*
|
||||
* For security, be sure to declare any new methods as protected or private.
|
||||
*/
|
||||
abstract class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Be sure to declare properties for any property fetch you initialized.
|
||||
* The creation of dynamic property is deprecated in PHP 8.2.
|
||||
*/
|
||||
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
// Load here all helpers you want to be available in your controllers that extend BaseController.
|
||||
// Caution: Do not put the this below the parent::initController() call below.
|
||||
// $this->helpers = ['form', 'url'];
|
||||
|
||||
// Caution: Do not edit this line.
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
// Preload any models, libraries, etc, here.
|
||||
$this->session = service('session');
|
||||
}
|
||||
|
||||
public function renderTwig($template, $data = [])
|
||||
{
|
||||
helper('csrf');
|
||||
|
||||
$twig = \Config\Services::twig();
|
||||
|
||||
// oldInput из сессии добавляется в данные шаблона
|
||||
// Расширение TwigGlobalsExtension автоматически добавляет session, alerts, old, currentOrg
|
||||
$oldInput = $this->session->get('_ci_old_input') ?? [];
|
||||
$data['old'] = $data['old'] ?? $oldInput;
|
||||
|
||||
ob_start();
|
||||
$twig->display($template, $data);
|
||||
$content = ob_get_clean();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Методы для универсальных таблиц
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Конфигурация таблицы - переопределяется в каждом контроллере
|
||||
*/
|
||||
protected function getTableConfig(): array
|
||||
{
|
||||
return [
|
||||
'model' => null,
|
||||
'columns' => [],
|
||||
'searchable' => [],
|
||||
'sortable' => [],
|
||||
'defaultSort' => 'id',
|
||||
'order' => 'asc',
|
||||
'viewPath' => '',
|
||||
'partialPath' => '',
|
||||
'itemsKey' => 'items',
|
||||
'scope' => null, // callable($builder) для дополнительных модификаций
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка AJAX запроса
|
||||
*/
|
||||
protected function isAjax(): bool
|
||||
{
|
||||
$header = $this->request->header('X-Requested-With');
|
||||
$value = $header ? $header->getValue() : '';
|
||||
return strtolower($value) === 'xmlhttprequest';
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготовка данных таблицы (общая логика для всех таблиц)
|
||||
*/
|
||||
protected function prepareTableData(?array $config = null): array
|
||||
{
|
||||
$config = array_merge($this->getTableConfig(), $config ?? []);
|
||||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||||
$perPage = (int) ($this->request->getGet('perPage') ?? 10);
|
||||
$sort = $this->request->getGet('sort') ?? $config['defaultSort'];
|
||||
$order = $this->request->getGet('order') ?? $config['order'];
|
||||
|
||||
// Исправление: получаем фильтры из параметра filters[]
|
||||
$filters = [];
|
||||
$rawFilters = $this->request->getGet('filters');
|
||||
|
||||
if ($rawFilters) {
|
||||
if (is_array($rawFilters)) {
|
||||
$filters = $rawFilters;
|
||||
} else {
|
||||
// Для обратной совместимости, если фильтры пришли в строке
|
||||
parse_str($rawFilters, $filters);
|
||||
if (isset($filters['filters'])) {
|
||||
$filters = $filters['filters'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Старый способ извлечения фильтров для совместимости
|
||||
foreach ($this->request->getGet() as $key => $value) {
|
||||
if (str_starts_with($key, 'filters[') && str_ends_with($key, ']')) {
|
||||
$field = substr($key, 8, -1); // Исправлено: было 9, должно быть 8
|
||||
$filters[$field] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$model = $config['model'];
|
||||
$builder = $model->builder();
|
||||
|
||||
// Сбрасываем все предыдущие условия
|
||||
$builder->resetQuery();
|
||||
|
||||
if (isset($config['scope']) && is_callable($config['scope'])) {
|
||||
$config['scope']($builder);
|
||||
}
|
||||
|
||||
// Применяем фильтры
|
||||
foreach ($filters as $field => $value) {
|
||||
if ($value !== '' && in_array($field, $config['searchable'])) {
|
||||
$builder->like($field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// Сортировка
|
||||
if ($sort && in_array($sort, $config['sortable'])) {
|
||||
$builder->orderBy($sort, $order);
|
||||
}
|
||||
|
||||
// Исправлено: countAllResults(false) вместо countAll()
|
||||
// Сохраняем текущее состояние builder для подсчета
|
||||
$countBuilder = clone $builder;
|
||||
$total = $countBuilder->countAllResults(false);
|
||||
|
||||
// Получаем данные с пагинацией
|
||||
$builder->select('*');
|
||||
$items = $builder->limit($perPage, ($page - 1) * $perPage)->get()->getResult();
|
||||
|
||||
$from = ($page - 1) * $perPage + 1;
|
||||
$to = min($page * $perPage, $total);
|
||||
|
||||
$pagerData = [
|
||||
'currentPage' => $page,
|
||||
'pageCount' => $total > 0 ? (int) ceil($total / $perPage) : 1,
|
||||
'total' => $total,
|
||||
'perPage' => $perPage,
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
];
|
||||
|
||||
$pagerStub = new class($pagerData) {
|
||||
private $data;
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
public function getCurrentPage(): int
|
||||
{
|
||||
return $this->data['currentPage'] ?? 1;
|
||||
}
|
||||
public function getPageCount(): int
|
||||
{
|
||||
return $this->data['pageCount'] ?? 1;
|
||||
}
|
||||
public function getTotal(): int
|
||||
{
|
||||
return $this->data['total'] ?? 0;
|
||||
}
|
||||
public function getDetails(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
};
|
||||
|
||||
$data = [
|
||||
$config['itemsKey'] => $items,
|
||||
'pager' => $pagerStub,
|
||||
'pagerDetails' => $pagerData,
|
||||
'perPage' => $perPage,
|
||||
'sort' => $sort,
|
||||
'order' => $order,
|
||||
'filters' => $filters,
|
||||
'columns' => $config['columns'],
|
||||
];
|
||||
|
||||
// В конце prepareTableData, перед return
|
||||
log_message('debug', 'Total records calculated: ' . $total);
|
||||
log_message('debug', 'Organization ID: ' . session()->get('active_org_id'));
|
||||
log_message('debug', 'SQL Query: ' . $countBuilder->getCompiledSelect());
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX endpoint для таблицы - возвращает partial (tbody + tfoot)
|
||||
*/
|
||||
public function table()
|
||||
{
|
||||
$config = $this->getTableConfig();
|
||||
$data = $this->prepareTableData($config);
|
||||
|
||||
if (!$this->isAjax()) {
|
||||
return redirect()->to('/');
|
||||
}
|
||||
|
||||
return $this->renderTwig($config['partialPath'], $data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user