MAD Lab
/
Tutor > Listagens e tabelas > Data table

Data table

Listagens e tabelas

mad-data-table
mad_validate acusa R045 nestas 4 demos — falso positivo
items, query, model, database, filters, order-by, limit, group-by e group-mask não estão no schema do mad_props (nem na lista de props conhecidas do R045) — mas existem de verdade em MadGridCompiler::compileDataTableBlock (packages/mad-framework/src/mad/grid/ MadGridCompiler.php) e são exatamente o que essas demos usam. Metadado desatualizado, não erro de marcação — vale reportar pra quem mantém o schema do MCP.
Fonte :items (array PHP)

Sem model — array já achatado (cliente/categoria em string) por TutorGridsDataTable::pedidosResumo().

Pedido Cliente Categoria Data Valor
Pedido #128 Rafael Pereira Brinquedos 28/09/2026 R$ 6.685,43
Pedido #99 Rafael Pereira Eletrônicos 24/09/2026 R$ 5.963,62
Pedido #84 Gabriela Pereira Eletrônicos 18/09/2026 R$ 7.745,58
Pedido #18 Fábio Souza Vestuário 16/09/2026 R$ 703,92
Pedido #108 Nelson Araújo Móveis 14/09/2026 R$ 2.321,95
Pedido #55 Ana Souza Brinquedos 11/09/2026 R$ 4.464,35
Pedido #63 Karina Souza Alimentos 11/09/2026 R$ 7.133,89
Pedido #141 Hugo Almeida Eletrônicos 09/09/2026 R$ 5.400,52
R$ 40.419,26
resources/views/tutor/examples/grids-data-table/items.blade.php
@php
    // Partial de <x-tutor-demo> = escopo Blade ISOLADO — `$that` não
    // atravessa esse aninhamento sozinho (ver grids-basico/paginacao.blade.php
    // para a explicação completa). Reobtido via o stack estático de
    // MadRenderContext (push/pop em MadComponent::render()).
    $that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-data-table :items="$that->pedidosResumo(8)" zebra>
    <mad-col field="pedido" label="Pedido" />
    <mad-col field="cliente" label="Cliente" />
    <mad-col field="categoria" label="Categoria" width="140px" />
    <mad-col field="data" label="Data" date="d/m/Y" width="110px" />
    <mad-col field="valor" label="Valor" right money="R$" total="sum" width="140px" />
</mad-data-table>
app/control/Tutor/TutorGridsDataTable.php
/**
 * Linhas achatadas (fonte do exemplo `:items`) — cliente/categoria já
 * resolvidos em string aqui, porque `:items` não lazy-loada relação.
 */
public function pedidosResumo(int $limit = 30): array
{
    return TutorDemoPedido::query()
        ->with(['cliente', 'categoria'])
        ->orderByDesc('dt_pedido')
        ->limit($limit)
        ->get()
        ->map(fn (TutorDemoPedido $p) => [
            'pedido'    => $p->descricao,
            'cliente'   => $p->cliente->nome ?? '—',
            'categoria' => $p->categoria->nome ?? '—',
            'data'      => $p->dt_pedido,
            'valor'     => (float) $p->valor_total,
        ])
        ->all();
}
Fonte :query (Builder Eloquent)

Builder pronto com with(); aqui {relacao->campo} resolve sozinho — os itens são Model de verdade.

Cód. Cliente Estado Data Valor
128 Rafael Pereira Cancelado 28/09/2026 R$ 6.685,43
99 Rafael Pereira Cancelado 24/09/2026 R$ 5.963,62
84 Gabriela Pereira Entregue 18/09/2026 R$ 7.745,58
18 Fábio Souza Enviado 16/09/2026 R$ 703,92
108 Nelson Araújo Entregue 14/09/2026 R$ 2.321,95
55 Ana Souza Entregue 11/09/2026 R$ 4.464,35
63 Karina Souza Cancelado 11/09/2026 R$ 7.133,89
141 Hugo Almeida Entregue 09/09/2026 R$ 5.400,52
R$ 40.419,26
resources/views/tutor/examples/grids-data-table/query.blade.php
@php
    // Partial de <x-tutor-demo> = escopo Blade ISOLADO — `$that` não
    // atravessa esse aninhamento sozinho (ver grids-basico/paginacao.blade.php
    // para a explicação completa). Reobtido via o stack estático de
    // MadRenderContext (push/pop em MadComponent::render()).
    $that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-data-table :query="$that->pedidosQuery()" order-by="dt_pedido desc" limit="8" zebra bordered>
    <mad-col field="id" label="Cód." width="60" right />
    <mad-col field="{cliente->nome}" label="Cliente" />
    <mad-col field="{estado->nome}" label="Estado" />
    <mad-col field="dt_pedido" label="Data" date="d/m/Y" width="110px" />
    <mad-col field="valor_total" label="Valor" right money="R$" total="sum" width="140px" />
</mad-data-table>
app/control/Tutor/TutorGridsDataTable.php
/**
 * Builder pronto (fonte do exemplo `:query`) — aqui `{relacao->campo}`
 * resolve sozinho no Blade porque os itens são Model de verdade;
 * with() evita N+1 (a coluna ainda dispara lazy-load se você esquecer).
 */
public function pedidosQuery(): Builder
{
    return TutorDemoPedido::query()->with(['cliente', 'estado']);
}
Fonte model= direto na tag (zero PHP)

model + database + :filters (DSL) resolvidos pelo próprio componente, sem método no controller.

Nome E-mail Cidade UF
Ana Souza ana.souza@tutor.test São Paulo SP
Ana Souza ana.souza21@tutor.test São Paulo SP
Bruno Pereira bruno.pereira@tutor.test Campinas SP
Bruno Pereira bruno.pereira22@tutor.test Campinas SP
resources/views/tutor/examples/grids-data-table/model.blade.php
<mad-data-table model="TutorDemoCliente" database="business"
    :filters="[['uf', '=', 'SP']]" order-by="nome" limit="8" compact>
    <mad-col field="nome" label="Nome" />
    <mad-col field="email" label="E-mail" />
    <mad-col field="cidade" label="Cidade" />
    <mad-col field="uf" label="UF" width="60" center />
</mad-data-table>
Agrupamento (group-by + group-mask + group-total)

Mesma fonte :items da 1ª demo, agrupada por categoria com subtotal por grupo.

Pedido Cliente Data Valor
Categoria: Brinquedos (7) Total: R$ 29.430,74
Pedido #128 Rafael Pereira 28/09/2026 R$ 6.685,43
Pedido #55 Ana Souza 11/09/2026 R$ 4.464,35
Pedido #104 Mariana Almeida 26/08/2026 R$ 3.130,64
Pedido #46 Mariana Almeida 02/08/2026 R$ 2.184,07
Pedido #35 Gabriela Pereira 28/07/2026 R$ 4.576,59
Pedido #25 Otávio Gomes 24/07/2026 R$ 1.850,76
Pedido #96 Otávio Gomes 10/07/2026 R$ 6.538,90
Subtotal Categoria: Brinquedos R$ 29.430,74
Categoria: Eletrônicos (9) Total: R$ 42.207,45
Pedido #99 Rafael Pereira 24/09/2026 R$ 5.963,62
Pedido #84 Gabriela Pereira 18/09/2026 R$ 7.745,58
Pedido #141 Hugo Almeida 09/09/2026 R$ 5.400,52
Pedido #64 Nelson Araújo 06/09/2026 R$ 846,66
Pedido #94 João Gomes 02/09/2026 R$ 5.894,35
Pedido #40 Vanessa Gomes 01/08/2026 R$ 3.282,15
Pedido #109 Lucas Pereira 26/07/2026 R$ 2.830,10
Pedido #97 Gabriela Pereira 22/07/2026 R$ 5.533,03
Pedido #115 Ana Souza 16/07/2026 R$ 4.711,44
Subtotal Categoria: Eletrônicos R$ 42.207,45
Categoria: Vestuário (4) Total: R$ 15.179,86
Pedido #18 Fábio Souza 16/09/2026 R$ 703,92
Pedido #27 Mariana Almeida 06/09/2026 R$ 5.617,90
Pedido #117 Isabela Araújo 03/09/2026 R$ 807,20
Pedido #121 João Gomes 01/09/2026 R$ 8.050,84
Subtotal Categoria: Vestuário R$ 15.179,86
Categoria: Móveis (1) Total: R$ 2.321,95
Pedido #108 Nelson Araújo 14/09/2026 R$ 2.321,95
Subtotal Categoria: Móveis R$ 2.321,95
Categoria: Alimentos (4) Total: R$ 26.493,48
Pedido #63 Karina Souza 11/09/2026 R$ 7.133,89
Pedido #36 Diego Araújo 02/09/2026 R$ 5.981,71
Pedido #74 Otávio Gomes 11/07/2026 R$ 7.654,26
Pedido #147 Karina Souza 03/07/2026 R$ 5.723,62
Subtotal Categoria: Alimentos R$ 26.493,48
Categoria: Livros (3) Total: R$ 15.755,48
Pedido #150 Carla Almeida 10/08/2026 R$ 4.095,50
Pedido #52 Otávio Gomes 01/08/2026 R$ 9.330,57
Pedido #6 Carla Almeida 04/07/2026 R$ 2.329,41
Subtotal Categoria: Livros R$ 15.755,48
Categoria: Ferramentas (2) Total: R$ 6.741,57
Pedido #31 Diego Araújo 22/07/2026 R$ 1.400,24
Pedido #90 Hugo Almeida 10/07/2026 R$ 5.341,33
Subtotal Categoria: Ferramentas R$ 6.741,57
R$ 138.130,53
resources/views/tutor/examples/grids-data-table/agrupamento.blade.php
@php
    // Partial de <x-tutor-demo> = escopo Blade ISOLADO — `$that` não
    // atravessa esse aninhamento sozinho (ver grids-basico/paginacao.blade.php
    // para a explicação completa). Reobtido via o stack estático de
    // MadRenderContext (push/pop em MadComponent::render()).
    $that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-data-table :items="$that->pedidosResumo(30)" group-by="categoria"
    group-mask="Categoria: {categoria}" group-total zebra>
    <mad-col field="pedido" label="Pedido" />
    <mad-col field="cliente" label="Cliente" />
    <mad-col field="data" label="Data" date="d/m/Y" width="110px" />
    <mad-col field="valor" label="Valor" right money="R$" total="sum" width="140px" />
</mad-data-table>
Código-fonte completo da página
app/control/Tutor/TutorGridsDataTable.php
<?php

namespace App\Control\Tutor;

use App\Models\TutorDemo\TutorDemoPedido;
use App\Support\Tutor\TutorDemoSeed;
use App\Support\Tutor\TutorPage;
use Illuminate\Database\Eloquent\Builder;

/**
 * TutorGridsDataTable — catálogo grids_data_table (tag única: mad-data-table).
 *
 * <mad-data-table> é read-only: sem actions, edit, search ou paginação.
 * Fonte dos dados, por precedência (packages/mad-framework/src/mad/grid/
 * MadGridCompiler.php::compileDataTableBlock + GridRenderHelpers::
 * renderDataTable): `:query` (Builder pronto) > `model=` (auto-query da tag)
 * > `:items` (array PHP).
 *
 * GOTCHA validado na fonte (GridRenderHelpers::normalizeRows): `{relacao->
 * campo}` só resolve sozinho quando o item por trás é um objeto Eloquent
 * (fontes `:query` e `model=`, que sempre entregam Model). Com `:items`
 * (array puro) não existe objeto pra disparar o lazy-load — por isso
 * pedidosResumo() abaixo já entrega os nomes de cliente/categoria
 * achatados em string, e não usa a notação `{relacao->campo}`.
 */
class TutorGridsDataTable extends TutorPage
{
    protected string $catalogKey = 'grids_data_table';

    public function mount(array $params = []): void
    {
        TutorDemoSeed::ensure();
    }

    /**
     * Linhas achatadas (fonte do exemplo `:items`) — cliente/categoria já
     * resolvidos em string aqui, porque `:items` não lazy-loada relação.
     */
    public function pedidosResumo(int $limit = 30): array
    {
        return TutorDemoPedido::query()
            ->with(['cliente', 'categoria'])
            ->orderByDesc('dt_pedido')
            ->limit($limit)
            ->get()
            ->map(fn (TutorDemoPedido $p) => [
                'pedido'    => $p->descricao,
                'cliente'   => $p->cliente->nome ?? '—',
                'categoria' => $p->categoria->nome ?? '—',
                'data'      => $p->dt_pedido,
                'valor'     => (float) $p->valor_total,
            ])
            ->all();
    }

    /**
     * Builder pronto (fonte do exemplo `:query`) — aqui `{relacao->campo}`
     * resolve sozinho no Blade porque os itens são Model de verdade;
     * with() evita N+1 (a coluna ainda dispara lazy-load se você esquecer).
     */
    public function pedidosQuery(): Builder
    {
        return TutorDemoPedido::query()->with(['cliente', 'estado']);
    }
}
resources/views/tutor/grids/data-table.blade.php
<x-tutor-page page="grids_data_table">
    <mad-callout type="warning" title="mad_validate acusa R045 nestas 4 demos — falso positivo" icon="alert-triangle">
        <code>items</code>, <code>query</code>, <code>model</code>, <code>database</code>,
        <code>filters</code>, <code>order-by</code>, <code>limit</code>, <code>group-by</code> e
        <code>group-mask</code> não estão no schema do <code>mad_props</code> (nem na lista de
        props conhecidas do R045) — mas existem de verdade em
        <code>MadGridCompiler::compileDataTableBlock</code> (packages/mad-framework/src/mad/grid/
        MadGridCompiler.php) e são exatamente o que essas demos usam. Metadado desatualizado, não
        erro de marcação — vale reportar pra quem mantém o schema do MCP.
    </mad-callout>

    <x-tutor-demo demo="tutor.examples.grids-data-table.items"
        title="Fonte :items (array PHP)"
        desc="Sem model — array já achatado (cliente/categoria em string) por TutorGridsDataTable::pedidosResumo()."
        php="TutorGridsDataTable::pedidosResumo" />

    <x-tutor-demo demo="tutor.examples.grids-data-table.query"
        title="Fonte :query (Builder Eloquent)"
        desc="Builder pronto com with(); aqui {relacao->campo} resolve sozinho — os itens são Model de verdade."
        php="TutorGridsDataTable::pedidosQuery" />

    <x-tutor-demo demo="tutor.examples.grids-data-table.model"
        title="Fonte model= direto na tag (zero PHP)"
        desc="model + database + :filters (DSL) resolvidos pelo próprio componente, sem método no controller." />

    <x-tutor-demo demo="tutor.examples.grids-data-table.agrupamento"
        title="Agrupamento (group-by + group-mask + group-total)"
        desc="Mesma fonte :items da 1ª demo, agrupada por categoria com subtotal por grupo." />
</x-tutor-page>