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 | 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>