MAD Lab
/
Tutor > Módulos visuais > Árvore (tree view)

Árvore (tree view)

Módulos visuais

mad-tree-view mad-tree-actions mad-tree-context-menu
Básico

mad-tree-view com auto-query no model, expand/collapse e clique por nó.

Diretoria
Financeiro
Contas a pagar
Contas a receber
Tesouraria
Comercial
Vendas
Marketing
Operações
Logística
Produção
resources/views/tutor/examples/modulos-tree-view/basico.blade.php
<mad-tree-view name="departamentos" model="TutorDemoTreeNode" display="nome"
    parent-field="parent_id" icon="folder" active-icon="folder-open"
    order-by="ordem" mad:click="onSelectTreeNode" persist />
app/control/Tutor/TutorModulosTreeView.php
public function onSelectTreeNode(int $id): MadResponse
{
    $nome = TutorDemoTreeNode::query()->find($id)?->nome ?? "#{$id}";

    return (new MadResponse())->toast("Selecionado: {$nome}", 'info');
}
Dados manuais

Árvore declarativa via :items — dispensa model quando os dados já existem em memória.

Categorias
Eletrônicos
Papelaria
Fornecedores
resources/views/tutor/examples/modulos-tree-view/itens-manuais.blade.php
@php
    $arvore = [
        ['id' => 1, 'name' => 'Categorias', 'icon' => 'folder', '_count' => 2, 'children' => [
            ['id' => 2, 'name' => 'Eletrônicos', 'icon' => 'file', '_count' => 12, 'children' => []],
            ['id' => 3, 'name' => 'Papelaria', 'icon' => 'file', '_count' => 5, 'children' => []],
        ]],
        ['id' => 4, 'name' => 'Fornecedores', 'icon' => 'file', '_count' => 0, 'children' => []],
    ];
@endphp
<mad-tree-view name="categorias-estaticas" :items="$arvore" display="name"
    icon="folder" count-field="_count" expanded="all" />
Ações e menu de contexto

mad-tree-actions (botão global acima da árvore) + mad-tree-context-menu (ações por nó).

Diretoria
Financeiro
Contas a pagar
Contas a receber
Tesouraria
Comercial
Vendas
Marketing
Operações
Logística
Produção
resources/views/tutor/examples/modulos-tree-view/acoes-contexto.blade.php
<mad-tree-view name="departamentos-acoes" model="TutorDemoTreeNode" display="nome"
    parent-field="parent_id" icon="folder" order-by="ordem" mad:click="onSelectTreeNode">

    <mad-tree-actions position="top">
        <mad-btn variant="ghost" size="sm" icon="plus" mad:click="onNovoDepartamento">Novo departamento</mad-btn>
    </mad-tree-actions>

    <mad-tree-context-menu>
        <mad-menu-item icon="pencil" mad:click="onRenomearNode({id})">Renomear</mad-menu-item>
        <mad-menu-separator />
        <mad-menu-item icon="trash-2" variant="danger" mad:click="onExcluirNode({id})">Excluir</mad-menu-item>
    </mad-tree-context-menu>
</mad-tree-view>
app/control/Tutor/TutorModulosTreeView.php
public function onExcluirNode(int $id): MadResponse
{
    $nome = TutorDemoTreeNode::query()->find($id)?->nome ?? "#{$id}";

    return (new MadResponse())->toast("Excluir \"{$nome}\" — ação de exemplo (nada foi apagado).", 'warning');
}
Ícones dinâmicos e contagem

icon-field por nó, active/expanded-icon e badge de contagem via count-field.

Diretoria
Financeiro3
Contas a pagar
Contas a receber
Tesouraria
Comercial2
Vendas
Marketing
Operações2
Logística
Produção
resources/views/tutor/examples/modulos-tree-view/icones-contagem.blade.php
<mad-tree-view name="departamentos-badges" model="TutorDemoTreeNode" display="nome"
    parent-field="parent_id" icon="folder" icon-field="icone" active-icon="folder-open"
    expanded-icon="folder-open" count-field="itens_count" order-by="ordem" expanded="first" />
Código-fonte completo da página
app/control/Tutor/TutorModulosTreeView.php
<?php

namespace App\Control\Tutor;

use App\Models\TutorDemo\TutorDemoTreeNode;
use App\Support\Tutor\TutorPage;
use Illuminate\Support\Facades\Schema;
use Mad\Http\MadResponse;

/**
 * TutorModulosTreeView — <mad-tree-view> + <mad-tree-actions> +
 * <mad-tree-context-menu>.
 *
 * Fixture própria (tutor_tree_nodes, molde KanbanDevView::ensureFixture) —
 * nenhuma tabela do TutorDemoSeed compartilhado é hierárquica (parent_id),
 * então esta página tem seu próprio model+seed, só para hierarquia.
 */
class TutorModulosTreeView extends TutorPage
{
    protected string $catalogKey = 'modulos_tree_view';

    public function mount(array $params = []): void
    {
        $this->ensureFixture();
    }

    // ── Ações do menu de contexto / toolbar (ilustrativas — não persistem) ──

    public function onSelectTreeNode(int $id): MadResponse
    {
        $nome = TutorDemoTreeNode::query()->find($id)?->nome ?? "#{$id}";

        return (new MadResponse())->toast("Selecionado: {$nome}", 'info');
    }

    public function onNovoDepartamento(): MadResponse
    {
        return (new MadResponse())
            ->toast('Ação de exemplo — normalmente abriria um form de novo departamento.', 'info');
    }

    public function onRenomearNode(int $id): MadResponse
    {
        $nome = TutorDemoTreeNode::query()->find($id)?->nome ?? "#{$id}";

        return (new MadResponse())->toast("Renomear \"{$nome}\" — ação de exemplo.", 'info');
    }

    public function onExcluirNode(int $id): MadResponse
    {
        $nome = TutorDemoTreeNode::query()->find($id)?->nome ?? "#{$id}";

        return (new MadResponse())->toast("Excluir \"{$nome}\" — ação de exemplo (nada foi apagado).", 'warning');
    }

    // ── Fixture ──────────────────────────────────────────────────────────

    private function ensureFixture(): void
    {
        $schema = Schema::connection('business');

        if (!$schema->hasTable('tutor_tree_nodes')) {
            $schema->create('tutor_tree_nodes', function ($t) {
                $t->increments('id');
                $t->unsignedInteger('parent_id')->nullable();
                $t->string('nome');
                $t->string('icone')->nullable();
                $t->unsignedInteger('itens_count')->default(0);
                $t->unsignedInteger('ordem')->default(0);
            });
        }

        if (TutorDemoTreeNode::query()->count() > 0) {
            return;
        }

        $ordem  = 0;
        $raizId = TutorDemoTreeNode::query()->create([
            'parent_id' => null, 'nome' => 'Diretoria', 'icone' => 'building-2',
            'itens_count' => 0, 'ordem' => $ordem++,
        ])->id;

        $departamentos = [
            'Financeiro' => ['Contas a pagar', 'Contas a receber', 'Tesouraria'],
            'Comercial'  => ['Vendas', 'Marketing'],
            'Operações'  => ['Logística', 'Produção'],
        ];

        foreach ($departamentos as $nome => $filhos) {
            $depId = TutorDemoTreeNode::query()->create([
                'parent_id' => $raizId, 'nome' => $nome, 'icone' => 'folder',
                'itens_count' => count($filhos), 'ordem' => $ordem++,
            ])->id;

            foreach ($filhos as $filho) {
                TutorDemoTreeNode::query()->create([
                    'parent_id' => $depId, 'nome' => $filho, 'icone' => 'file-text',
                    'itens_count' => 0, 'ordem' => $ordem++,
                ]);
            }
        }
    }
}
resources/views/tutor/modulos/tree-view.blade.php
<x-tutor-page page="modulos_tree_view">
    <x-tutor-demo demo="tutor.examples.modulos-tree-view.basico"
        title="Básico" desc="mad-tree-view com auto-query no model, expand/collapse e clique por nó."
        php="TutorModulosTreeView::onSelectTreeNode" />

    <x-tutor-demo demo="tutor.examples.modulos-tree-view.itens-manuais"
        title="Dados manuais" desc="Árvore declarativa via :items — dispensa model quando os dados já existem em memória." />

    <x-tutor-demo demo="tutor.examples.modulos-tree-view.acoes-contexto"
        title="Ações e menu de contexto" desc="mad-tree-actions (botão global acima da árvore) + mad-tree-context-menu (ações por nó)."
        php="TutorModulosTreeView::onExcluirNode" />

    <x-tutor-demo demo="tutor.examples.modulos-tree-view.icones-contagem"
        title="Ícones dinâmicos e contagem" desc="icon-field por nó, active/expanded-icon e badge de contagem via count-field." />
</x-tutor-page>