esolutions/datatable

Server-side DataTable, dialogs and Excel exports for Laravel

Maintainers

Package info

github.com/eriquegasparcarlos/esolutions-datatable

pkg:composer/esolutions/datatable

Transparency log

Statistics

Installs: 41

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.3.0 2026-08-19 15:23 UTC

This package is auto-updated.

Last update: 2026-08-19 15:28:37 UTC


README

Infraestructura para tablas server-side con paginación, filtros, columnas, diálogos de confirmación y exportación Excel.

Instalación

composer require esolutions/datatable

Namespace

Esolutions\Datatable\

Dependencias

  • esolutions/laravel — para ApiResponse y AuthHelper
  • maatwebsite/excel — para exportación Excel

Uso completo — ejemplo de un módulo

Cada módulo define un DataTable como trait y lo usa en su controlador:

// Modules/Plan/DataTables/PlanDataTable.php
use Esolutions\Datatable\Table\{Button, ButtonBuilder, Column, ColumnBuilder, Filter, FilterBuilder};
use Esolutions\Datatable\Traits\PaginationSystemTrait;

trait PlanDataTable
{
    use PaginationSystemTrait;

    protected function getTableConfig(): array
    {
        return [
            'page_title'  => __('plans title page'),
            'table_title' => __('plans title table'),
            'table_name'  => 'plans',
        ];
    }

    protected function getHeaderButtons(): array
    {
        return (new ButtonBuilder())
            ->addButton(Button::newButton())
            ->addButton(Button::refreshButton())
            ->addButtonGroup([Button::exportButton()])
            ->getButtons();
    }

    protected function getColumns(): array
    {
        return (new ColumnBuilder())
            ->addColumn(Column::make('name')->label('Nombre'))
            ->addColumn(Column::make('price')->label('Precio')->alignRight()->width('100px'))
            ->addColumn(Column::isActive())
            ->addColumn(Column::actions())
            ->getColumns();
    }

    protected function getFilters(): array
    {
        return (new FilterBuilder())
            ->addFilter(Filter::makeInput('search')->label('Buscar')->cssClass('col-12'))
            ->getFilters();
    }
}
// Modules/Plan/Http/Controllers/PlanController.php
use Esolutions\Datatable\Dialog\DialogAction;
use Esolutions\Datatable\Dialog\Requests\ActionRequest;
use Esolutions\Laravel\Auth\AuthHelper;
use Esolutions\Laravel\Http\ApiResponse;

class PlanController extends Controller
{
    use PlanDataTable;

    // GET /plans/record/{id}
    public function record($id) { ... }

    // POST /plans/store
    public function store(PlanRequest $request) { ... }

    // GET /plans/record-active/{id}
    public function recordActive($id)
    {
        $record = Plan::findOrFail($id);
        return DialogAction::getActiveRecordActionData($record, 'name', 'plan');
    }

    // POST /plans/store-active
    public function storeActive(ActionRequest $request)
    {
        $password = $request->input('password');
        if ($password && !AuthHelper::checkPassword($password)) {
            return ApiResponse::error('La contraseña es incorrecta', 403);
        }
        $record = Plan::findOrFail($request->input('id'));
        $record->update(['is_active' => !$record->is_active]);
        return ApiResponse::success($record->is_active ? 'Activado' : 'Desactivado');
    }

    // GET /plans/record-delete/{id}
    public function recordDelete($id)
    {
        $record = Plan::findOrFail($id);
        return DialogAction::getDeleteRecordActionData($record, 'name', 'plan');
    }

    // POST /plans/delete
    public function storeDelete(ActionRequest $request)
    {
        $password = $request->input('password');
        if ($password && !AuthHelper::checkPassword($password)) {
            return ApiResponse::error('La contraseña es incorrecta', 403);
        }
        Plan::findOrFail($request->input('id'))->delete();
        return ApiResponse::success('Plan eliminado');
    }
}

Dialog — DialogAction

Genera la configuración del diálogo de confirmación que el frontend renderiza.

getDeleteRecordActionData

DialogAction::getDeleteRecordActionData(
    record: $record,
    nameField: 'name',       // campo que muestra el nombre en el diálogo
    type: 'plan',            // texto del tipo de registro
    verifyPassword: false    // true = el frontend muestra campo de contraseña
);

Retorna:

{
    "title": "Eliminar plan",
    "description": "¿Está seguro que desea eliminar el plan <strong>Básico</strong>?",
    "button_label_submit": "Eliminar",
    "button_color": "red",
    "icon": "warning",
    "icon_color": "red",
    "verify_password": false
}

getActiveRecordActionData

DialogAction::getActiveRecordActionData($record, 'name', 'plan');

Detecta el estado actual (is_active) y retorna el texto correspondiente (Activar / Desactivar).

getActionData

Para acciones personalizadas:

DialogAction::getActionData(
    title: 'Sincronizar datos',
    description: '¿Desea sincronizar con SUNAT?',
    options: ['button_color' => 'primary', 'verify_password' => true]
);

Dialog — ActionRequest

FormRequest estándar para los endpoints storeDelete y storeActive.

Reglas de validación:

Campo Regla
id required
password nullable, required_if:verify_password,true

Table — Columnas

Column

use Esolutions\Datatable\Table\Column;

Column::make('name')->label('Nombre')
Column::make('price')->label('Precio')->alignRight()->width('100px')
Column::make('date')->label('Fecha')->alignCenter()
Column::isActive()    // columna estándar de estado activo/inactivo
Column::actions()     // columna estándar de botones de acción

ColumnBuilder

use Esolutions\Datatable\Table\ColumnBuilder;

$columns = (new ColumnBuilder())
    ->addColumn(Column::make('name')->label('Nombre'))
    ->addColumn(Column::isActive())
    ->addColumn(Column::actions())
    ->getColumns();

Table — Filtros

Filter

use Esolutions\Datatable\Table\Filter;

Filter::makeInput('search')->label('Buscar')->cssClass('col-12')
Filter::makeSelect('status')->label('Estado')->options([
    ['id' => 'all', 'name' => 'Todos'],
    ['id' => 1,     'name' => 'Activo'],
    ['id' => 0,     'name' => 'Inactivo'],
])

FilterBuilder

use Esolutions\Datatable\Table\FilterBuilder;

$filters = (new FilterBuilder())
    ->addFilter(Filter::makeInput('search')->label('Buscar')->cssClass('col-24 col-sm-12'))
    ->getFilters();

Table — Botones de cabecera

use Esolutions\Datatable\Table\{Button, ButtonBuilder};

$buttons = (new ButtonBuilder())
    ->addButton(Button::newButton())
    ->addButton(Button::refreshButton())
    ->addButtonGroup([Button::exportButton()])
    ->getButtons();
Botón Descripción
Button::newButton() Abre el formulario de creación
Button::refreshButton() Recarga la tabla
Button::exportButton() Exporta a Excel

Iconos

->icon() recibe un rol, no el nombre de un icono de una librería concreta:

Button::make()->label('Guardar')->icon('save')

Quién dibuja el icono lo decide el frontend (x-components), que trae los suyos como SVG. Hasta la v2.1.0 este paquete prefijaba fal fa- por su cuenta, lo que ataba el backend a FontAwesome Pro.

Los roles disponibles están en icons/roles.json de x-components; los más usados desde acá son add, edit, delete, view, copy, refresh, download, activate, deactivate, warning, info, blocked.

Los nombres de FontAwesome (fal fa-trash) pasan intactos al frontend, que los deja dibujar al FontAwesome del proyecto: un proyecto que todavía los tenga escritos no se rompe. Y un icono de dominio que no tenga rol (user-hoodie, money-bill) se puede mandar pelado — x-components lo entrega como fa-light fa-<nombre>.

Requiere @esolutions/x-components >= 2.17.0. Con una version anterior el frontend no resuelve los roles y los botones quedan sin icono.

Traits de paginación

PaginationSystemTrait

Para tablas en el contexto del sistema central (base de datos central).

PaginationTenantTrait

Para tablas dentro del contexto de un tenant.

use Esolutions\Datatable\Traits\PaginationSystemTrait;
// o
use Esolutions\Datatable\Traits\PaginationTenantTrait;

Ambos proveen: updatePagination(), $perPage, $sortBy, $direction, $metaAdditional.

Exports — GenericReportExport

Exportador Excel reutilizable con título, encabezados estilizados y datos.

use Esolutions\Datatable\Exports\GenericReportExport;
use Maatwebsite\Excel\Facades\Excel;

public function exportRecords(Request $request)
{
    $records = Plan::all()->map(fn($r) => [$r->name, $r->price])->toArray();

    return Excel::download(
        new GenericReportExport(
            data: $records,
            headings: ['Nombre', 'Precio'],
            title: 'Reporte de Planes'
        ),
        'reporte_planes.xlsx'
    );
}

Formato del Excel generado:

  • Fila 1: Título centrado, negrita, tamaño 18, combinado en todas las columnas
  • Fila 2: Encabezados con fondo azul #1976d2, texto blanco, negrita
  • Filas de datos: Bordes grises, texto envuelto, columnas auto-dimensionadas