dashboard for org

This commit is contained in:
root
2026-01-12 11:58:12 +03:00
parent d27f66953c
commit 246ca93307
30 changed files with 4362 additions and 125 deletions
+97 -15
View File
@@ -7,8 +7,9 @@ use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
use App\Models\OrganizationModel;
use App\Services\AccessService;
/**
/**
* BaseController provides a convenient place for loading components
* and performing functions that are needed by all your controllers.
*
@@ -27,6 +28,7 @@ abstract class BaseController extends Controller
*/
protected $session;
protected AccessService $access;
/**
* @return void
@@ -42,6 +44,33 @@ abstract class BaseController extends Controller
// Preload any models, libraries, etc, here.
$this->session = service('session');
$this->access = service('access');
// Загружаем хелпер доступа для Twig
helper('access');
}
/**
* Проверка права на действие ( shortcut для $this->access->can() )
*
* @param string $action
* @param string $resource
* @return bool
*/
protected function can(string $action, string $resource): bool
{
return $this->access->can($action, $resource);
}
/**
* Проверка роли (shortcut для $this->access->isRole() )
*
* @param string|array $roles
* @return bool
*/
protected function isRole($roles): bool
{
return $this->access->isRole($roles);
}
public function renderTwig($template, $data = [])
@@ -55,6 +84,9 @@ abstract class BaseController extends Controller
$oldInput = $this->session->get('_ci_old_input') ?? [];
$data['old'] = $data['old'] ?? $oldInput;
// Добавляем access в данные шаблона для функций can(), isRole() и т.д.
$data['access'] = $this->access;
ob_start();
$twig->display($template, $data);
$content = ob_get_clean();
@@ -129,19 +161,39 @@ abstract class BaseController extends Controller
}
$model = $config['model'];
$builder = $model->builder();
// Сбрасываем все предыдущие условия
$builder->resetQuery();
// Если есть кастомный scope - создаём новый чистый запрос
// scope будет полностью контролировать FROM, JOIN, SELECT
if (isset($config['scope']) && is_callable($config['scope'])) {
$builder = $model->db()->newQuery();
$config['scope']($builder);
} else {
// Стандартный путь - используем builder модели
$builder = $model->builder();
$builder->resetQuery();
// Автоматическая фильтрация по организации для моделей с TenantScopedModel
$modelClass = get_class($model);
$traits = class_uses($modelClass);
if (in_array('App\Models\Traits\TenantScopedModel', $traits)) {
$model->forCurrentOrg();
}
}
// Применяем фильтры
foreach ($filters as $field => $value) {
if ($value !== '' && in_array($field, $config['searchable'])) {
$builder->like($field, $value);
foreach ($filters as $filterKey => $value) {
if ($value === '') {
continue;
}
// Сначала проверяем fieldMap (алиасы) — они имеют приоритет
if (isset($config['fieldMap']) && isset($config['fieldMap'][$filterKey])) {
$realField = $config['fieldMap'][$filterKey];
$builder->like($realField, $value);
}
// Потом проверяем прямое совпадение
elseif (in_array($filterKey, $config['searchable'])) {
$builder->like($filterKey, $value);
}
}
@@ -150,13 +202,11 @@ abstract class BaseController extends Controller
$builder->orderBy($sort, $order);
}
// Исправлено: countAllResults(false) вместо countAll()
// Сохраняем текущее состояние builder для подсчета
$countBuilder = clone $builder;
$total = $countBuilder->countAllResults(false);
// Получаем данные с пагинацией
$builder->select('*');
// Получаем данные с пагинацией (scope уже установил нужный SELECT)
$items = $builder->limit($perPage, ($page - 1) * $perPage)->get()->getResultArray();
$from = ($page - 1) * $perPage + 1;
@@ -180,6 +230,8 @@ abstract class BaseController extends Controller
'filters' => $filters,
'columns' => $config['columns'],
'actionsConfig' => $config['actionsConfig'] ?? [],
'can_edit' => $config['can_edit'] ?? true,
'can_delete' => $config['can_delete'] ?? true,
];
return $data;
@@ -221,12 +273,42 @@ abstract class BaseController extends Controller
}
/**
* AJAX endpoint для таблицы - возвращает partial (tbody + tfoot)
* Если запрос не AJAX - возвращает полную таблицу
* AJAX endpoint для таблицы
*
* Логика:
* - Если format=partial или AJAX → возвращает только tbody + tfoot
* - Если прямой GET → редиректит на основную страницу с теми же параметрами
*
* @param array|null $config Кастомная конфигурация таблицы (если null, используется getTableConfig())
* @param string|null $pageUrl URL основной страницы таблицы (для редиректа)
* @return string|ResponseInterface
*/
public function table()
public function table(?array $config = null, ?string $pageUrl = null)
{
$isPartial = $this->request->getGet('format') === 'partial' || $this->isAjax();
return $this->renderTable(null, $isPartial);
// Если это частичный запрос (AJAX) — возвращаем только таблицу
if ($isPartial) {
return $this->renderTable($config, true);
}
// Прямой запрос к /table — редиректим на основную страницу
// Сохраняем все параметры: page, perPage, sort, order, filters
$params = $this->request->getGet();
unset($params['format']); // Убираем format=partial если был
if ($pageUrl) {
$redirectUrl = $pageUrl;
} else {
// Пытаемся извлечь URL из config['url']
$tableUrl = $config['url'] ?? '/table';
$redirectUrl = $tableUrl;
}
if (!empty($params)) {
$redirectUrl .= '?' . http_build_query($params);
}
return redirect()->to($redirectUrl);
}
}