Dashboards e widgets
mad-dashboard
mad-db-blocks
mad-db-blocks-row
mad-dashboard-tabs
Stack vertical
mad-dashboard empilha seções com gap consistente — dispensa margin-bottom manual em cada bloco.
Vendas do mês
Cada seção aqui dentro (mad-card, mad-form-grid, mad-grid...) recebe o mesmo espaçamento vertical automaticamente.
Clientes ativos
Sem mad-dashboard, cada bloco precisaria de margin-bottom manual — e alguém sempre esquece um.
resources/views/tutor/examples/dashboards-layout/stack-vertical.blade.php
<mad-dashboard :gap="20">
<mad-card header="Vendas do mês">
<p style="margin:0;color:var(--mad-muted);">Cada seção aqui dentro (mad-card, mad-form-grid, mad-grid...) recebe o mesmo espaçamento vertical automaticamente.</p>
</mad-card>
<mad-card header="Clientes ativos">
<p style="margin:0;color:var(--mad-muted);">Sem mad-dashboard, cada bloco precisaria de margin-bottom manual — e alguém sempre esquece um.</p>
</mad-card>
</mad-dashboard>
Quadro de avisos
mad-db-blocks em modo flat (sem registro-mestre) + mad-db-blocks-row como template inline da linha — adicionar/remover sem nenhum drawer.
Nenhum aviso fixado ainda.
resources/views/tutor/examples/dashboards-layout/avisos-fixados.blade.php
<mad-db-blocks name="notas_layout" mode="flat" pivot-model="TutorLayoutNota" database="business"
order-by="id" order-dir="desc" layout="stack"
add-mode="inline" add-label="+ Fixar aviso" empty-text="Nenhum aviso fixado ainda."
on-add="onNotaAdd">
<mad-db-blocks-row>
<div class="tutor-nota tutor-nota-{{ $item->tipo }}">
<span>{{ $item->texto }}</span>
<button type="button" data-mad-click="blockRemove({{ (int) $item->id }}, '{{ $state }}')" aria-label="Remover aviso">×</button>
</div>
</mad-db-blocks-row>
<mad-db-blocks-form>
<mad-form-grid :cols="2">
<mad-input-field name="texto" label="Texto do aviso" required placeholder="Ex.: Fechamento mensal em 3 dias" />
<mad-select-field name="tipo" label="Tipo" :items="['info' => 'Info', 'success' => 'Sucesso', 'warning' => 'Atenção', 'error' => 'Crítico']" />
</mad-form-grid>
</mad-db-blocks-form>
</mad-db-blocks>
app/control/Tutor/TutorDashboardsLayout.php
/**
* on-add hook do <mad-db-blocks mode="flat"> — único cuidado é não
* aceitar um aviso vazio (o form do bloco não tem `required` nativo
* disponível fora de um <mad-form>).
*/
public function onNotaAdd(TutorLayoutNota $pivot): bool
{
return trim((string) ($pivot->texto ?? '')) !== '';
}
Abas com carga lazy
mad-dashboard-tabs busca cada aba por AJAX (X-Mad-Partial) só quando o usuário clica — aqui cada aba é uma tela irmã deste catálogo.
resources/views/tutor/examples/dashboards-layout/abas-lazy.blade.php
@php($__component = \Mad\Component\MadRenderContext::getComponent())
{{-- Cada aba busca a tela irmã por fetch (X-Mad-Partial) só quando clicada —
por isso o conteúdo real da aba só aparece depois do clique; abrir a
página estática (sem JS) mostra só a tira de abas + os panes vazios.
history="off" porque aqui é uma demo embutida — numa tela real, deixar
o default sincroniza ?tab= com a URL. --}}
<div style="max-height:420px;overflow:auto;border:1px solid var(--mad-border);border-radius:10px;">
<mad-dashboard-tabs name="tutor_layout_tabs" :tabs="$__component->tabsDoCatalogo()"
active="metricas" variant="underline" history="off" prefetch="none" />
</div>
Código-fonte completo da página
app/control/Tutor/TutorDashboardsLayout.php
<?php
namespace App\Control\Tutor;
use App\Models\TutorDemo\TutorLayoutNota;
use App\Support\Tutor\TutorPage;
use Illuminate\Support\Facades\Schema;
use Mad\Form\MadDbBlocksTrait;
use Mad\Routing\MadRoutes;
/**
* TutorDashboardsLayout — página "Layout de dashboard" do catálogo.
*
* Cobre mad-dashboard (stack vertical), mad-db-blocks + mad-db-blocks-row
* (quadro de avisos em modo "flat" — sem registro-mestre) e
* mad-dashboard-tabs (abas lazy apontando para as telas irmãs deste mesmo
* catálogo, via MadRoutes::urlFor — sem criar rota nova).
*/
class TutorDashboardsLayout extends TutorPage
{
use MadDbBlocksTrait;
protected string $catalogKey = 'dashboards_layout';
public function mount(array $params = []): void
{
$this->ensureNotasFixture();
}
/**
* URLs das telas irmãs do catálogo — usadas como panes lazy do exemplo de
* <mad-dashboard-tabs>. Não cria rota nova: reaproveita as rotas
* `tutor_dashboards_*` já expostas em routes/modules/tutor.php.
*/
public function tabsDoCatalogo(): array
{
return [
[
'name' => 'metricas',
'label' => 'Métricas',
'icon' => 'gauge',
'url' => MadRoutes::urlFor('TutorDashboardsMetricas'),
],
[
'name' => 'charts',
'label' => 'Gráficos',
'icon' => 'bar-chart-3',
'url' => MadRoutes::urlFor('TutorDashboardsCharts'),
],
[
'name' => 'widgets',
'label' => 'Widgets',
'icon' => 'layout-grid',
'url' => MadRoutes::urlFor('TutorDashboardsWidgets'),
],
];
}
/**
* on-add hook do <mad-db-blocks mode="flat"> — único cuidado é não
* aceitar um aviso vazio (o form do bloco não tem `required` nativo
* disponível fora de um <mad-form>).
*/
public function onNotaAdd(TutorLayoutNota $pivot): bool
{
return trim((string) ($pivot->texto ?? '')) !== '';
}
/**
* Fixture própria da página (mesmo molde de
* App\Control\Sys\DashboardDevView::ensureFixture()) — NÃO é a fixture
* compartilhada TutorDemoSeed, então não entra nela.
*/
private function ensureNotasFixture(): void
{
$schema = Schema::connection('business');
if (!$schema->hasTable('tutor_layout_notas')) {
$schema->create('tutor_layout_notas', function ($t) {
$t->increments('id');
$t->string('texto');
$t->string('tipo', 16)->default('info');
$t->timestamp('created_at')->useCurrent();
});
}
}
}
resources/views/tutor/dashboards/layout.blade.php
<x-tutor-page page="dashboards_layout">
<x-tutor-demo demo="tutor.examples.dashboards-layout.stack-vertical"
title="Stack vertical" desc="mad-dashboard empilha seções com gap consistente — dispensa margin-bottom manual em cada bloco." />
<x-tutor-demo demo="tutor.examples.dashboards-layout.avisos-fixados"
title="Quadro de avisos" desc="mad-db-blocks em modo flat (sem registro-mestre) + mad-db-blocks-row como template inline da linha — adicionar/remover sem nenhum drawer."
php="TutorDashboardsLayout::onNotaAdd" />
<x-tutor-demo demo="tutor.examples.dashboards-layout.abas-lazy"
title="Abas com carga lazy" desc="mad-dashboard-tabs busca cada aba por AJAX (X-Mad-Partial) só quando o usuário clica — aqui cada aba é uma tela irmã deste catálogo." />
</x-tutor-page>