757 lines
38 KiB
Markdown
757 lines
38 KiB
Markdown
# Компонент динамических таблиц DataTable
|
||
|
||
## Общее описание
|
||
|
||
Компонент DataTable представляет собой универсальную систему для отображения интерактивных таблиц с поддержкой AJAX-загрузки данных, сортировки по столбцам, поиска и пагинации. Система построена на трёх уровнях: серверная часть (контроллер с методом `prepareTableData`), клиентская часть (JavaScript-модуль DataTable) и уровень представления (компоненты Twig).
|
||
|
||
Архитектура компонента обеспечивает бесшовную работу как при серверном рендеринге (первичная загрузка страницы), так и при AJAX-обновлениях (фильтрация, сортировка, пагинация). При серверном рендеринге таблица отображается сразу с данными, при этом JavaScript автоматически определяет наличие данных и пропускает избыточный AJAX-запрос. При любых действиях пользователя (сортировка, фильтрация, переход по страницам) данные подгружаются через AJAX, а клиентский модуль обновляет только tbody и tfoot, сохраняя заголовок таблицы неизменным.
|
||
|
||
---
|
||
|
||
## Структура компонентов
|
||
|
||
### Файловая структура
|
||
|
||
Компонент таблицы состоит из нескольких файлов, организованных по функциональному признаку. JavaScript-модуль расположен в `public/assets/js/modules/DataTable.js` и отвечает за все интерактивные взаимодействия на клиенте. Стили находятся в `public/assets/css/modules/data-table.css` и обеспечивают визуальное оформление элементов управления таблицей. Шаблоны Twig размещены в директории `app/Views/components/table/` и включают основной компонент таблицы, заголовок, пагинацию и макросы для рендеринга действий.
|
||
|
||
Основные файлы компонента:
|
||
- `table.twig` — универсальный компонент таблицы, включающий заголовок, тело и футер с пагинацией
|
||
- `table_header.twig` — переиспользуемый заголовок с поддержкой сортировки и поиска
|
||
- `pagination.twig` — компонент пагинации с навигацией по страницам
|
||
- `ajax_table.twig` — упрощённый tbody для AJAX-ответов без заголовка
|
||
- `macros.twig` — Twig-макросы для рендеринга кнопок действий
|
||
|
||
### Интеграция с BaseController
|
||
|
||
Класс BaseController предоставляет готовую инфраструктуру для работы с таблицами через методы `getTableConfig()`, `prepareTableData()`, `renderTable()` и `table()`. Метод `getTableConfig()` возвращает конфигурацию таблицы, определяющую модель данных, колонки, правила поиска и сортировки, а также действия для каждой строки. Метод `prepareTableData()` выполняет всю логику обработки параметров запроса (пагинация, сортировка, фильтрация), формирует данные и возвращает их в структурированном виде для передачи в шаблон. Метод `renderTable()` принимает конфигурацию и возвращает HTML-код таблицы. Метод `table()` является HTTP-обработчиком для AJAX-запросов, который возвращает только tbody и tfoot без заголовка.
|
||
|
||
---
|
||
|
||
## Подключение в контроллере
|
||
|
||
### Конфигурация таблицы
|
||
|
||
Каждый контроллер модуля должен определить конфигурацию таблицы через метод `getTableConfig()`. Конфигурация представляет собой ассоциативный массив с обязательными и опциональными параметрами. Обязательными параметрами являются `model` (экземпляр модели для выборки данных) и `columns` (описание колонок таблицы). Опциональные параметры позволяют настроить поведение поиска, сортировки, действий и отображения пустого состояния.
|
||
|
||
```php
|
||
class Clients extends BaseController
|
||
{
|
||
protected ClientModel $clientModel;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->clientModel = new ClientModel();
|
||
}
|
||
|
||
protected function getTableConfig(): array
|
||
{
|
||
return [
|
||
'id' => 'clients-table',
|
||
'url' => '/clients/table',
|
||
'model' => $this->clientModel,
|
||
'columns' => [
|
||
'name' => ['label' => 'Имя / Название', 'width' => '40%'],
|
||
'email' => ['label' => 'Email', 'width' => '25%'],
|
||
'phone' => ['label' => 'Телефон', 'width' => '20%'],
|
||
'created_at' => ['label' => 'Создан', 'width' => '15%'],
|
||
],
|
||
'searchable' => ['name', 'email', 'phone'],
|
||
'sortable' => ['name', 'email', 'phone', 'created_at'],
|
||
'defaultSort' => 'name',
|
||
'order' => 'asc',
|
||
'actions' => ['label' => 'Действия', 'width' => '15%'],
|
||
'actionsConfig' => [
|
||
[
|
||
'label' => 'Редактировать',
|
||
'url' => '/clients/edit/{id}',
|
||
'icon' => 'fa-solid fa-pen',
|
||
'class' => 'btn-outline-primary',
|
||
'type' => 'edit',
|
||
],
|
||
[
|
||
'label' => 'Удалить',
|
||
'url' => '/clients/delete/{id}',
|
||
'icon' => 'fa-solid fa-trash',
|
||
'class' => 'btn-outline-danger',
|
||
'type' => 'delete',
|
||
'confirm' => 'Вы уверены, что хотите удалить этого клиента?',
|
||
],
|
||
],
|
||
'emptyMessage' => 'Клиентов пока нет',
|
||
'emptyIcon' => 'fa-solid fa-users',
|
||
'emptyActionUrl' => base_url('/clients/new'),
|
||
'emptyActionLabel' => 'Добавить клиента',
|
||
'emptyActionIcon' => 'fa-solid fa-plus',
|
||
'can_edit' => $this->access->canEdit('clients'),
|
||
'can_delete' => $this->access->canDelete('clients'),
|
||
];
|
||
}
|
||
}
|
||
```
|
||
|
||
### Параметры конфигурации
|
||
|
||
Параметр `id` задаёт уникальный идентификатор контейнера таблицы и используется для инициализации JavaScript-модуля. Параметр `url` определяет endpoint для AJAX-загрузки данных. Параметр `model` указывает экземпляр модели CodeIgniter, которая используется для запроса данных. Модель автоматически фильтруется по организации через трейт `TenantScopedModel` при его наличии.
|
||
|
||
Параметр `columns` описывает структуру колонок таблицы. Ключ массива соответствует имени поля в данных, а значение — ассоциативному массиву с параметрами отображения. Параметр `label` задаёт заголовок колонки, параметр `width` — ширину колонки в процентах или пикселях. Опционально можно указать `placeholder` для поля поиска, `searchTitle` для tooltip-подсказки и `align` для CSS-класса выравнивания содержимого.
|
||
|
||
Параметр `searchable` определяет массив имён колонок, по которым разрешён поиск. Эти колонки получат иконку поиска в заголовке. Параметр `sortable` определяет массив имён колонок, по которым разрешена сортировка. При клике по заголовку сортируемой колонки таблица пересортируется по этому полю. Параметры `defaultSort` и `order` задают поле и направление сортировки по умолчанию.
|
||
|
||
### Методы контроллера для таблицы
|
||
|
||
Основной метод для отображения страницы с таблицей выглядит следующим образом:
|
||
|
||
```php
|
||
public function index()
|
||
{
|
||
// Проверка прав доступа
|
||
if (!$this->access->canView('clients')) {
|
||
return $this->forbiddenResponse('Нет прав для просмотра клиентов');
|
||
}
|
||
|
||
$config = $this->getTableConfig();
|
||
|
||
return $this->renderTwig('@Clients/index', [
|
||
'title' => 'Клиенты',
|
||
'tableHtml' => $this->renderTable($config),
|
||
'can_create' => $this->access->canCreate('clients'),
|
||
'can_edit' => $this->access->canEdit('clients'),
|
||
'can_delete' => $this->access->canDelete('clients'),
|
||
]);
|
||
}
|
||
```
|
||
|
||
Метод для AJAX-загрузки данных таблицы использует встроенную логику BaseController:
|
||
|
||
```php
|
||
public function table(?array $config = null, ?string $pageUrl = null)
|
||
{
|
||
// Проверка прав доступа
|
||
if (!$this->access->canView('clients')) {
|
||
return $this->forbiddenResponse('Нет прав для просмотра клиентов');
|
||
}
|
||
|
||
return parent::table($config, '/clients');
|
||
}
|
||
```
|
||
|
||
Метод `table()` автоматически определяет тип запроса (обычный или AJAX) и возвращает либо полную таблицу, либо только tbody и tfoot соответственно. Для определения типа запроса используется заголовок `X-Requested-With` или параметр `format=partial`.
|
||
|
||
---
|
||
|
||
## Подключение в шаблоне
|
||
|
||
### Базовое подключение
|
||
|
||
Для подключения таблицы в шаблоне Twig используется компонент `table.twig`. Компонент принимает данные из метода `renderTable()` контроллера, который возвращает полностью сформированный HTML:
|
||
|
||
```twig
|
||
{# app/Modules/Clients/Views/index.twig #}
|
||
|
||
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block title %}Клиенты{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="page-header d-flex justify-content-between align-items-center mb-4">
|
||
<h1 class="page-title">Клиенты</h1>
|
||
{% if can_create %}
|
||
<a href="{{ url('/clients/new') }}" class="btn btn-primary">
|
||
<i class="fa-solid fa-plus me-2"></i>Добавить клиента
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-body p-0">
|
||
{{ tableHtml|raw }}
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
{{ parent() }}
|
||
<script src="{{ url('/assets/js/modules/DataTable.js') }}"></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
new DataTable('clients-table', {
|
||
url: '/clients/table',
|
||
perPage: 10,
|
||
debounceTime: 300,
|
||
preserveSearchOnSort: true
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|
||
```
|
||
|
||
### Прямое использование компонента
|
||
|
||
При необходимости таблицу можно подключить напрямую через `include`, передав все параметры вручную:
|
||
|
||
```twig
|
||
{% from '@components/table/macros.twig' import render_actions %}
|
||
|
||
<div class="table-responsive">
|
||
{{ include('@components/table/table.twig', {
|
||
id: 'my-table',
|
||
url: '/my-module/table',
|
||
perPage: 25,
|
||
sort: sort|default(''),
|
||
order: order|default('asc'),
|
||
filters: filters|default({}),
|
||
items: items,
|
||
pagerDetails: pagerDetails,
|
||
columns: {
|
||
name: { label: 'Название', width: '40%' },
|
||
email: { label: 'Email', width: '30%' },
|
||
status: { label: 'Статус', width: '20%' },
|
||
},
|
||
actions: { label: 'Действия', width: '10%' },
|
||
actionsConfig: [
|
||
{ label: 'Ред.', url: '/edit/{id}', icon: 'fa-solid fa-pen', class: 'btn-outline-primary' },
|
||
],
|
||
can_edit: can_edit|default(true),
|
||
can_delete: can_delete|default(true),
|
||
emptyMessage: 'Записей не найдено',
|
||
emptyIcon: 'fa-solid fa-inbox',
|
||
emptyActionUrl: url('/create'),
|
||
emptyActionLabel: 'Создать',
|
||
emptyActionIcon: 'fa-solid fa-plus',
|
||
onRowClick: 'openClientDetails',
|
||
tableClass: 'table-sm',
|
||
}) }}
|
||
</div>
|
||
```
|
||
|
||
### Поддержка render_cell и render_actions
|
||
|
||
В шаблонах Twig доступны глобальные функции для рендеринга ячеек и действий. Функция `render_cell()` автоматически обрабатывает значение ячейки в зависимости от типа данных:
|
||
|
||
```twig
|
||
{# Ячейка с автоматическим форматированием #}
|
||
<td>{{ render_cell(item, 'name')|raw }}</td>
|
||
<td>{{ render_cell(item, 'email')|raw }}</td>
|
||
<td>{{ render_cell(item, 'price')|raw }}</td>
|
||
<td>{{ render_cell(item, 'created_at')|raw }}</td>
|
||
|
||
{# Ячейка с кастомным классом #}
|
||
{{ render_cell(item, 'status', { class: 'badge bg-success' })|raw }}
|
||
```
|
||
|
||
Функция `render_actions()` рендерит кнопки действий для строки таблицы:
|
||
|
||
```twig
|
||
{% set actions = [
|
||
{ label: 'Ред.', url: '/edit/' ~ item.id, icon: 'fa-solid fa-pen', class: 'btn-outline-primary' },
|
||
{ label: 'Удалить', url: '/delete/' ~ item.id, icon: 'fa-solid fa-trash', class: 'btn-outline-danger' },
|
||
] %}
|
||
{{ render_actions(actions)|raw }}
|
||
```
|
||
|
||
---
|
||
|
||
## Конфигурация колонок
|
||
|
||
### Параметры колонки
|
||
|
||
Каждая колонка описывается массивом с возможными параметрами. Обязательным параметром является только `label`, остальные опциональны:
|
||
|
||
```php
|
||
'columns' => [
|
||
'name' => [
|
||
'label' => 'Название',
|
||
'width' => '40%',
|
||
'placeholder' => 'Поиск по названию',
|
||
'searchTitle' => 'Нажмите для поиска',
|
||
'align' => 'text-start',
|
||
],
|
||
'price' => [
|
||
'label' => 'Цена',
|
||
'width' => '15%',
|
||
'align' => 'text-end',
|
||
],
|
||
'status' => [
|
||
'label' => 'Статус',
|
||
'width' => '15%',
|
||
],
|
||
]
|
||
```
|
||
|
||
Параметр `width` задаёт ширину колонки и может быть указан в процентах или пикселях. Рекомендуется использовать проценты для адаптивности или комбинировать фиксированные и относительные значения. Сумма ширин всех колонок обычно должна составлять 100% с учётом колонки действий.
|
||
|
||
### Поля searchable и sortable
|
||
|
||
Массив `searchable` определяет поля, по которым разрешён поиск. При указании поля в этом массиве в заголовке колонки появится иконка поиска, при клике на которую отобразится поле ввода:
|
||
|
||
```php
|
||
'searchable' => ['name', 'email', 'phone', 'company'],
|
||
```
|
||
|
||
Массив `sortable` определяет поля, по которым разрешена сортировка. При клике по заголовку сортируемой колонки таблица пересортируется по этому полю, при повторном клике направление сортировки меняется на противоположное:
|
||
|
||
```php
|
||
'sortable' => ['name', 'email', 'phone', 'created_at', 'price'],
|
||
```
|
||
|
||
Важно: имена полей в `searchable` и `sortable` должны соответствовать ключам массива `columns` и именам полей в базе данных.
|
||
|
||
---
|
||
|
||
## Конфигурация действий
|
||
|
||
### Структура actionsConfig
|
||
|
||
Параметр `actionsConfig` определяет кнопки действий для каждой строки таблицы. Каждое действие описывается массивом с параметрами:
|
||
|
||
```php
|
||
'actionsConfig' => [
|
||
[
|
||
'label' => '',
|
||
'url' => '/clients/edit/{id}',
|
||
'icon' => 'fa-solid fa-pen',
|
||
'class' => 'btn-outline-primary',
|
||
'title' => 'Редактировать',
|
||
'type' => 'edit',
|
||
'confirm' => null,
|
||
],
|
||
[
|
||
'label' => '',
|
||
'url' => '/clients/delete/{id}',
|
||
'icon' => 'fa-solid fa-trash',
|
||
'class' => 'btn-outline-danger',
|
||
'title' => 'Удалить',
|
||
'type' => 'delete',
|
||
'confirm' => 'Вы уверены?',
|
||
],
|
||
],
|
||
```
|
||
|
||
Параметр `url` поддерживает подстановку `{id}` для автоматической замены на идентификатор записи. Параметр `type` используется для фильтрации действий по правам доступа: действия с `type === 'edit'` показываются только при `can_edit === true`, действия с `type === 'delete'` — только при `can_delete === true`. Параметр `confirm` добавляет подтверждение действия через стандартный `confirm()` в JavaScript.
|
||
|
||
### Кастомные действия
|
||
|
||
Помимо типовых действий редактирования и удаления, можно определять кастомные действия:
|
||
|
||
```php
|
||
'actionsConfig' => [
|
||
[
|
||
'label' => 'Просмотр',
|
||
'url' => '/clients/view/{id}',
|
||
'icon' => 'fa-solid fa-eye',
|
||
'class' => 'btn-outline-secondary',
|
||
'title' => 'Просмотр клиента',
|
||
],
|
||
[
|
||
'label' => 'Создать сделку',
|
||
'url' => '/deals/create?client_id={id}',
|
||
'icon' => 'fa-solid fa-file-contract',
|
||
'class' => 'btn-outline-success',
|
||
'title' => 'Создать сделку',
|
||
],
|
||
[
|
||
'label' => 'Записать',
|
||
'url' => '/bookings/new?client_id={id}',
|
||
'icon' => 'fa-solid fa-calendar',
|
||
'class' => 'btn-outline-primary',
|
||
'title' => 'Запись на приём',
|
||
],
|
||
],
|
||
```
|
||
|
||
---
|
||
|
||
## Клиентская инициализация
|
||
|
||
### Базовая инициализация
|
||
|
||
JavaScript-модуль DataTable инициализируется для каждой таблицы на странице. При инициализации передаются параметры конфигурации:
|
||
|
||
```javascript
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
new DataTable('clients-table', {
|
||
url: '/clients/table',
|
||
perPage: 10,
|
||
debounceTime: 300,
|
||
preserveSearchOnSort: true
|
||
});
|
||
});
|
||
```
|
||
|
||
Параметр `url` задаёт endpoint для AJAX-загрузки данных. Параметр `perPage` определяет количество записей на странице по умолчанию. Параметр `debounceTime` задаёт задержку в миллисекундах перед выполнением поиска (защита от частых запросов при вводе). Параметр `preserveSearchOnSort` определяет, сохранять ли видимость полей поиска при сортировке.
|
||
|
||
### Методы DataTable
|
||
|
||
После инициализации экземпляр DataTable предоставляет методы для программного управления таблицей:
|
||
|
||
```javascript
|
||
const table = new DataTable('my-table', options);
|
||
|
||
// Установка фильтра
|
||
table.setFilter('name', 'Поисковый запрос');
|
||
|
||
// Установка количества записей на странице
|
||
table.setPerPage(25);
|
||
|
||
// Переход на конкретную страницу
|
||
table.goToPage(3);
|
||
|
||
// Перезагрузка данных
|
||
table.loadData();
|
||
```
|
||
|
||
---
|
||
|
||
## Пустое состояние и действия
|
||
|
||
### Конфигурация пустого состояния
|
||
|
||
При отсутствии данных в таблице отображается пустое состояние с возможностью действия. Параметры конфигурации:
|
||
|
||
```php
|
||
'emptyMessage' => 'Клиентов пока нет',
|
||
'emptyIcon' => 'fa-solid fa-users',
|
||
'emptyActionUrl' => base_url('/clients/new'),
|
||
'emptyActionLabel' => 'Добавить клиента',
|
||
'emptyActionIcon' => 'fa-solid fa-plus',
|
||
```
|
||
|
||
Параметр `emptyMessage` задаёт текст сообщения. Параметр `emptyIcon` указывает FontAwesome-иконку для отображения над сообщением. Параметры `emptyActionUrl`, `emptyActionLabel` и `emptyActionIcon` определяют кнопку действия при пустом состоянии.
|
||
|
||
### Условное скрытие действия
|
||
|
||
Кнопка действия при пустом состоянии отображается только если у пользователя есть право на создание:
|
||
|
||
```php
|
||
'emptyActionUrl' => $this->access->canCreate('clients')
|
||
? base_url('/clients/new')
|
||
: null,
|
||
'emptyActionLabel' => $this->access->canCreate('clients')
|
||
? 'Добавить клиента'
|
||
: null,
|
||
```
|
||
|
||
---
|
||
|
||
## Обработка special fieldMap
|
||
|
||
### Проблема несоответствия имён полей
|
||
|
||
При работе с моделями часто возникает ситуация, когда имя поля в базе данных отличается от имени свойства в Twig-шаблоне или имени параметра для фильтрации. Например, поле `client_name` в базе данных должно отображаться как «Клиент» и фильтроваться по параметру `client`. Для решения этой проблемы используется параметр `fieldMap`:
|
||
|
||
```php
|
||
protected function getTableConfig(): array
|
||
{
|
||
return [
|
||
'id' => 'deals-table',
|
||
'url' => '/deals/table',
|
||
'model' => $this->dealModel,
|
||
'columns' => [
|
||
'client_name' => ['label' => 'Клиент', 'width' => '30%'],
|
||
'title' => ['label' => 'Сделка', 'width' => '25%'],
|
||
'amount' => ['label' => 'Сумма', 'width' => '15%'],
|
||
'stage' => ['label' => 'Этап', 'width' => '15%'],
|
||
'created_at' => ['label' => 'Создан', 'width' => '15%'],
|
||
],
|
||
'searchable' => ['client_name', 'title', 'stage'],
|
||
'sortable' => ['client_name', 'title', 'amount', 'stage', 'created_at'],
|
||
'defaultSort' => 'created_at',
|
||
'order' => 'desc',
|
||
// fieldMap для маппинга параметров фильтрации на реальные поля
|
||
'fieldMap' => [
|
||
'client' => 'client_name', // filters[client] -> client_name
|
||
'stage' => 'stage',
|
||
],
|
||
// ... остальные параметры
|
||
];
|
||
}
|
||
```
|
||
|
||
При использовании `fieldMap` параметры фильтрации из URL (`filters[client]`) автоматически маппятся на реальное поле базы данных (`client_name`). Это позволяет использовать понятные имена параметров в URL при сохранении корректных имён полей в запросе к базе данных.
|
||
|
||
---
|
||
|
||
## Кастомные scope для запросов
|
||
|
||
### Использование callable scope
|
||
|
||
Когда стандартной фильтрации недостаточно (например, нужны JOIN-ы с другими таблицами или сложные условия), можно использовать параметр `scope`. Это callable-функция, которая получает builder и полностью контролирует формирование запроса:
|
||
|
||
```php
|
||
protected function getTableConfig(): array
|
||
{
|
||
return [
|
||
'id' => 'deals-table',
|
||
'url' => '/deals/table',
|
||
'model' => $this->dealModel,
|
||
// ... columns, searchable, sortable и т.д.
|
||
|
||
// Кастомный scope для сложных запросов
|
||
'scope' => function($builder) {
|
||
$builder->resetQuery();
|
||
|
||
$builder->select('d.*, c.name as client_name, c.email as client_email')
|
||
->from('deals d')
|
||
->join('clients c', 'c.id = d.client_id', 'left')
|
||
->where('d.organization_id', session()->get('active_org_id'));
|
||
|
||
// Дополнительная фильтрация по статусу
|
||
$status = $this->request->getGet('filters[status]');
|
||
if ($status && $status !== 'all') {
|
||
$builder->where('d.status', $status);
|
||
}
|
||
|
||
// Фильтрация по диапазону дат
|
||
$dateFrom = $this->request->getGet('filters[date_from]');
|
||
$dateTo = $this->request->getGet('filters[date_to]');
|
||
if ($dateFrom) {
|
||
$builder->where('d.created_at >=', $dateFrom);
|
||
}
|
||
if ($dateTo) {
|
||
$builder->where('d.created_at <=', $dateTo . ' 23:59:59');
|
||
}
|
||
},
|
||
|
||
// fieldMap для JOIN-полей
|
||
'fieldMap' => [
|
||
'client' => 'c.name',
|
||
'client_email' => 'c.email',
|
||
],
|
||
];
|
||
}
|
||
```
|
||
|
||
При использовании `scope` параметр `model` игнорируется для построения запроса, и `scope` полностью контролирует SELECT, FROM, JOIN и WHERE. Параметры сортировки и фильтрации всё ещё применяются к builder после выполнения scope, поэтому в `fieldMap` нужно указывать полные имена полей с алиасами таблиц.
|
||
|
||
---
|
||
|
||
## Практические примеры
|
||
|
||
### Пример 1: Таблица клиентов
|
||
|
||
```php
|
||
class Clients extends BaseController
|
||
{
|
||
protected ClientModel $clientModel;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->clientModel = new ClientModel();
|
||
}
|
||
|
||
public function index()
|
||
{
|
||
if (!$this->access->canView('clients')) {
|
||
return $this->forbiddenResponse('Нет прав для просмотра');
|
||
}
|
||
|
||
return $this->renderTwig('@Clients/index', [
|
||
'title' => 'Клиенты',
|
||
'tableHtml' => $this->renderTable($this->getTableConfig()),
|
||
'can_create' => $this->access->canCreate('clients'),
|
||
]);
|
||
}
|
||
|
||
public function table()
|
||
{
|
||
if (!$this->access->canView('clients')) {
|
||
return $this->forbiddenResponse('Нет прав');
|
||
}
|
||
return parent::table($this->getTableConfig(), '/clients');
|
||
}
|
||
|
||
protected function getTableConfig(): array
|
||
{
|
||
return [
|
||
'id' => 'clients-table',
|
||
'url' => '/clients/table',
|
||
'model' => $this->clientModel,
|
||
'columns' => [
|
||
'name' => ['label' => 'Имя / Название', 'width' => '35%'],
|
||
'email' => ['label' => 'Email', 'width' => '25%'],
|
||
'phone' => ['label' => 'Телефон', 'width' => '20%'],
|
||
'source' => ['label' => 'Источник', 'width' => '10%'],
|
||
'created_at' => ['label' => 'Создан', 'width' => '10%'],
|
||
],
|
||
'searchable' => ['name', 'email', 'phone'],
|
||
'sortable' => ['name', 'email', 'phone', 'source', 'created_at'],
|
||
'defaultSort' => 'created_at',
|
||
'order' => 'desc',
|
||
'actions' => ['label' => '', 'width' => '5%'],
|
||
'actionsConfig' => [
|
||
[
|
||
'label' => '',
|
||
'url' => '/clients/edit/{id}',
|
||
'icon' => 'fa-solid fa-pen',
|
||
'class' => 'btn-outline-primary btn-sm',
|
||
'title' => 'Редактировать',
|
||
'type' => 'edit',
|
||
],
|
||
[
|
||
'label' => '',
|
||
'url' => '/clients/delete/{id}',
|
||
'icon' => 'fa-solid fa-trash',
|
||
'class' => 'btn-outline-danger btn-sm',
|
||
'title' => 'Удалить',
|
||
'type' => 'delete',
|
||
'confirm' => 'Удалить клиента?',
|
||
],
|
||
],
|
||
'emptyMessage' => 'Клиентов пока нет',
|
||
'emptyIcon' => 'fa-solid fa-users',
|
||
'emptyActionUrl' => $this->access->canCreate('clients') ? '/clients/new' : null,
|
||
'emptyActionLabel' => $this->access->canCreate('clients') ? 'Добавить клиента' : null,
|
||
'emptyActionIcon' => 'fa-solid fa-plus',
|
||
'can_edit' => $this->access->canEdit('clients'),
|
||
'can_delete' => $this->access->canDelete('clients'),
|
||
];
|
||
}
|
||
}
|
||
```
|
||
|
||
### Пример 2: Таблица с кастомным рендерингом ячеек
|
||
|
||
```php
|
||
protected function getTableConfig(): array
|
||
{
|
||
return [
|
||
'id' => 'deals-table',
|
||
'url' => '/deals/table',
|
||
'model' => $this->dealModel,
|
||
'columns' => [
|
||
'client_name' => ['label' => 'Клиент', 'width' => '25%'],
|
||
'title' => ['label' => 'Сделка', 'width' => '25%'],
|
||
'amount' => ['label' => 'Сумма', 'width' => '15%'],
|
||
'stage' => ['label' => 'Этап', 'width' => '15%'],
|
||
'status' => ['label' => 'Статус', 'width' => '10%'],
|
||
'created_at' => ['label' => 'Создан', 'width' => '10%'],
|
||
],
|
||
'searchable' => ['client_name', 'title', 'stage'],
|
||
'sortable' => ['client_name', 'title', 'amount', 'stage', 'created_at'],
|
||
'defaultSort' => 'created_at',
|
||
'order' => 'desc',
|
||
'actions' => ['label' => '', 'width' => '5%'],
|
||
'actionsConfig' => [
|
||
[
|
||
'label' => '',
|
||
'url' => '/deals/edit/{id}',
|
||
'icon' => 'fa-solid fa-pen',
|
||
'class' => 'btn-outline-primary btn-sm',
|
||
'type' => 'edit',
|
||
],
|
||
],
|
||
'can_edit' => $this->access->canEdit('deals'),
|
||
];
|
||
}
|
||
```
|
||
|
||
В шаблоне Twig можно добавить кастомный рендеринг ячеек через Twig-фильтры:
|
||
|
||
```twig
|
||
{# В шаблоне ячейки с форматированием #}
|
||
<td>
|
||
<strong>{{ item.title }}</strong>
|
||
{% if item.description %}
|
||
<br><small class="text-muted">{{ item.description|slice(0, 50) }}...</small>
|
||
{% endif %}
|
||
</td>
|
||
<td class="text-end">
|
||
{{ item.amount|number_format(0, ',', ' ') }} ₽
|
||
</td>
|
||
<td>
|
||
{% if item.status == 'active' %}
|
||
<span class="badge bg-success">Активна</span>
|
||
{% elseif item.status == 'won' %}
|
||
<span class="badge bg-primary">Выиграна</span>
|
||
{% elseif item.status == 'lost' %}
|
||
<span class="badge bg-danger">Проиграна</span>
|
||
{% endif %}
|
||
</td>
|
||
```
|
||
|
||
---
|
||
|
||
## Проверка при создании модуля
|
||
|
||
### Чек-лист при добавлении новой таблицы
|
||
|
||
При создании нового модуля с таблицей необходимо выполнить следующие действия:
|
||
|
||
**В контроллере:**
|
||
- Определить метод `getTableConfig()` с обязательными параметрами (`id`, `url`, `model`, `columns`)
|
||
- Указать `searchable` и `sortable` массивы с корректными именами полей
|
||
- Настроить `actionsConfig` с кнопками действий и проверкой прав
|
||
- Добавить метод `table()` для AJAX-загрузки данных
|
||
- Вызвать `parent::table()` для использования встроенной логики
|
||
- Проверить права доступа перед вызовом родительского метода
|
||
|
||
**В шаблоне:**
|
||
- Подключить DataTable.js в блоке `scripts`
|
||
- Инициализировать DataTable с корректным `id` и `url`
|
||
- Передать `tableHtml` из контроллера в шаблон
|
||
- Убедиться, что CSS стили таблицы подключены
|
||
|
||
**Модель:**
|
||
- Использовать трейт `TenantScopedModel` для автоматической фильтрации по организации
|
||
- Убедиться, что модель имеет поле `organization_id`
|
||
- Проверить, что модель возвращает данные в ожидаемом формате
|
||
|
||
---
|
||
|
||
## Типичные ошибки и их устранение
|
||
|
||
### Таблица не загружается
|
||
|
||
Если данные не загружаются, проверьте следующее:
|
||
- URL в конфигурации и при инициализации DataTable должны совпадать
|
||
- Метод `table()` контроллера должен вызывать `parent::table()`
|
||
- Модель должна использовать трейт `TenantScopedModel` или обрабатывать фильтрацию вручную
|
||
- Проверьте консоль браузера на наличие ошибок JavaScript
|
||
- Убедитесь, что CSRF-токен передаётся корректно
|
||
|
||
### Сортировка не работает
|
||
|
||
Если сортировка не работает:
|
||
- Поле должно быть указано в массиве `sortable`
|
||
- Имя поля в `sortable` должно соответствовать ключу в `columns` и имени поля в базе данных
|
||
- Для JOIN-запросов используйте алиасы таблиц в `sortable` (`c.name` вместо `client_name`)
|
||
|
||
### Поиск не работает
|
||
|
||
Если поиск не работает:
|
||
- Поле должно быть указано в массиве `searchable`
|
||
- При использовании JOIN проверьте `fieldMap` для маппинга параметров
|
||
- Убедитесь, что в контроллере используется метод `like()` для фильтрации
|
||
|
||
### Действия не отображаются
|
||
|
||
Если кнопки действий не отображаются:
|
||
- Проверьте `can_edit` и `can_delete` в конфигурации
|
||
- Убедитесь, что `type` действия соответствует проверяемому праву (`'edit'` или `'delete'`)
|
||
- Проверьте параметр `actions` в конфигурации (должен быть `{label: 'Действия'}` или `true`)
|
||
|
||
---
|
||
|
||
## Сводка параметров конфигурации
|
||
|
||
| Параметр | Тип | Обязательный | Описание |
|
||
|----------|-----|--------------|----------|
|
||
| `id` | string | Да | Идентификатор контейнера таблицы |
|
||
| `url` | string | Да | URL для AJAX-загрузки |
|
||
| `model` | Model | Да | Экземпляр модели CodeIgniter |
|
||
| `columns` | array | Да | Конфигурация колонок |
|
||
| `searchable` | array | Нет | Поля для поиска |
|
||
| `sortable` | array | Нет | Поля для сортировки |
|
||
| `defaultSort` | string | Нет | Поле сортировки по умолчанию |
|
||
| `order` | string | Нет | Направление сортировки по умолчанию |
|
||
| `actions` | array\|bool | Нет | Конфигурация колонки действий |
|
||
| `actionsConfig` | array | Нет | Кнопки действий |
|
||
| `emptyMessage` | string | Нет | Сообщение при отсутствии данных |
|
||
| `emptyIcon` | string | Нет | Иконка при пустом состоянии |
|
||
| `emptyActionUrl` | string | Нет | URL действия при пустом состоянии |
|
||
| `emptyActionLabel` | string | Нет | Текст кнопки действия |
|
||
| `can_edit` | bool | Нет | Разрешено ли редактирование |
|
||
| `can_delete` | bool | Нет | Разрешено ли удаление |
|
||
| `fieldMap` | array | Нет | Маппинг параметров фильтрации |
|
||
| `scope` | callable | Нет | Кастомный запрос к базе данных |
|