MAD Lab
/
Tutor > Dashboards e widgets > Layout de dashboard

Layout de dashboard

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">&times;</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>