Módulos visuais
mad-org-chart
mad-org-chart-node
mad-org-chart-card
Árvore básica
mad-org-chart com título, subtítulo e métrica — zero PHP (fixture TutorDemoOrgFuncionario, conexão business).
resources/views/tutor/examples/modulos-org-chart/basico.blade.php
<mad-org-chart model="TutorDemoOrgFuncionario" database="business"
parent-field="chefe_id" title="nome" subtitle="cargo"
metric="equipe" metric-label="pessoas na equipe" order-field="nome" />
Card customizado
mad-org-chart-card com Blade arbitrário por nó ($item, $id, $chart em escopo).
resources/views/tutor/examples/modulos-org-chart/card-customizado.blade.php
<mad-org-chart model="TutorDemoOrgFuncionario" database="business"
parent-field="chefe_id" order-field="nome">
<mad-org-chart-card>
<strong>{{ $item->nome }}</strong><br>
<span style="color:var(--mad-text-muted); font-size:12px;">{{ $item->cargo }}</span>
@if ($item->equipe > 0)
<div style="margin-top:4px;">
<span class="tutor-chip">{{ $item->equipe }} na equipe</span>
</div>
@endif
</mad-org-chart-card>
</mad-org-chart>
Drag re-parent + lazy-load
draggable e lazy-depth exigem o HOST da página (TutorModulosOrgChart::afterReparent recalcula a métrica dos dois chefes envolvidos).
resources/views/tutor/examples/modulos-org-chart/drag-lazy.blade.php
@php
// Partial de <x-tutor-demo> = escopo Blade ISOLADO (component nativo) —
// `$that` (host que os compiladores dos widgets grandes leem via
// `$that ?? null`) não atravessa esse aninhamento sozinho. Reabre aqui
// porque draggable/lazy-depth só funcionam contra o HOST de verdade
// (TutorModulosOrgChart, subclasse de MadOrgChart — ver comentário da
// classe): sem isto, a tag cairia num MadOrgChartStandalone novo a cada
// request e o drag/expand nunca chegaria em onReparent/onLoadChildren.
$that = \Mad\Component\MadRenderContext::getComponent();
@endphp
{{-- Tag inline sem atributos reusa a config já declarada nas props
protected da classe (model/parentField/draggable/lazyDepth=1). --}}
<mad-org-chart />
app/control/Tutor/TutorModulosOrgChart.php
/**
* Após um re-parent (drag), o `equipe` (badge de métrica) dos dois chefes
* envolvidos fica desatualizado — recalcula pela contagem real de
* subordinados diretos. Contrato: OrgChartDevView não faz isso (não tinha
* necessidade); aqui é o exemplo de uso do hook.
*/
protected function afterReparent(object $node, int $oldParentId, int $newParentId): void
{
foreach (array_unique(array_filter([$oldParentId, $newParentId])) as $chefeId) {
TutorDemoOrgFuncionario::query()->whereKey($chefeId)->update([
'equipe' => TutorDemoOrgFuncionario::query()->where('chefe_id', $chefeId)->count(),
]);
}
}
Código-fonte completo da página
app/control/Tutor/TutorModulosOrgChart.php
<?php
namespace App\Control\Tutor;
use App\Models\TutorDemo\TutorDemoOrgFuncionario;
use Illuminate\Support\Facades\Schema;
use Mad\OrgChart\MadOrgChart;
/**
* TutorModulosOrgChart — página "Organograma" do MAD Lab (catálogo
* modulos_org_chart). Cobre: mad-org-chart, mad-org-chart-card.
*
* `mad-org-chart-node` (tag do catálogo) NÃO existe como componente público —
* é o NOME DO PARTIAL interno (components/org-chart-node.blade.php) que o
* próprio `<mad-org-chart>` usa por baixo pra recursão dos nós; confirmado via
* mad_props (mad-org-chart-node → "Componente não encontrado"). A tag
* documentada pra customizar o card por nó é `<mad-org-chart-card>`, coberta
* no demo "Card customizado" abaixo.
*
* ATENÇÃO (molde OrgChartDevView): draggable/lazy-depth só funcionam quando o
* HOST da página é subclasse de MadOrgChart (onLoadChildren/onReparent são
* métodos de instância) — por isso esta página estende MadOrgChart em vez de
* TutorPage. `view()` é reimplementado manualmente (perdemos o catalogKey
* automático de TutorPage, que não dá pra herdar junto — herança única).
*
* Só UM <mad-org-chart> pode ficar vinculado ao host por request: cada tag
* `_renderInlineOrgChart()` MUTA $this (model/parentField/draggable/...) e só
* a config do ÚLTIMO tag renderizado sobrevive pro round-trip do wire
* (onReparent/onLoadChildren). Os dois demos menores abaixo são renderizados
* como <mad-org-chart-card>/tag standalone (fora do host — ver partials, que
* NÃO reabrem `$that`) e o demo grande (draggable+lazy) é o ÚLTIMO da página.
*/
class TutorModulosOrgChart extends MadOrgChart
{
protected static string $wrapper = self::INTERNAL;
protected string $model = TutorDemoOrgFuncionario::class;
protected string $database = 'business';
protected string $parentField = 'chefe_id';
protected string $titleField = 'nome';
protected string $subtitleField = 'cargo';
protected string $metricField = 'equipe';
protected string $metricLabel = 'pessoas na equipe';
protected string $orderField = 'nome';
protected bool $draggable = true;
protected int $lazyDepth = 1;
public function mount(array $params = []): void
{
$this->ensureFixture();
parent::mount($params);
}
/**
* Após um re-parent (drag), o `equipe` (badge de métrica) dos dois chefes
* envolvidos fica desatualizado — recalcula pela contagem real de
* subordinados diretos. Contrato: OrgChartDevView não faz isso (não tinha
* necessidade); aqui é o exemplo de uso do hook.
*/
protected function afterReparent(object $node, int $oldParentId, int $newParentId): void
{
foreach (array_unique(array_filter([$oldParentId, $newParentId])) as $chefeId) {
TutorDemoOrgFuncionario::query()->whereKey($chefeId)->update([
'equipe' => TutorDemoOrgFuncionario::query()->where('chefe_id', $chefeId)->count(),
]);
}
}
// ── Fixture ──────────────────────────────────────────────────────────
private function ensureFixture(): void
{
$schema = Schema::connection('business');
if (!$schema->hasTable('tutor_org_funcionarios')) {
$schema->create('tutor_org_funcionarios', function ($t) {
$t->increments('id');
$t->string('nome');
$t->string('cargo')->nullable();
$t->integer('chefe_id')->nullable();
$t->integer('equipe')->default(0);
});
}
if (TutorDemoOrgFuncionario::query()->count() > 0) {
return;
}
TutorDemoOrgFuncionario::query()->insert([
['id' => 1, 'nome' => 'Ana Diretora', 'cargo' => 'CEO', 'chefe_id' => null, 'equipe' => 3],
['id' => 2, 'nome' => 'Bruno Gerente', 'cargo' => 'Gerente Comercial', 'chefe_id' => 1, 'equipe' => 2],
['id' => 3, 'nome' => 'Carla Gerente', 'cargo' => 'Gerente Financeiro', 'chefe_id' => 1, 'equipe' => 2],
['id' => 4, 'nome' => 'Diego Gerente', 'cargo' => 'Gerente de TI', 'chefe_id' => 1, 'equipe' => 3],
['id' => 5, 'nome' => 'Elisa Vendas', 'cargo' => 'Vendedora', 'chefe_id' => 2, 'equipe' => 0],
['id' => 6, 'nome' => 'Fabio Vendas', 'cargo' => 'Vendedor', 'chefe_id' => 2, 'equipe' => 0],
['id' => 7, 'nome' => 'Gina Contas', 'cargo' => 'Analista Financeiro', 'chefe_id' => 3, 'equipe' => 0],
['id' => 8, 'nome' => 'Hugo Contas', 'cargo' => 'Analista Financeiro', 'chefe_id' => 3, 'equipe' => 0],
['id' => 9, 'nome' => 'Iris Dev', 'cargo' => 'Dev Pleno', 'chefe_id' => 4, 'equipe' => 0],
['id' => 10, 'nome' => 'Joao Dev', 'cargo' => 'Dev Júnior', 'chefe_id' => 4, 'equipe' => 0],
['id' => 11, 'nome' => 'Karina Suporte', 'cargo' => 'Suporte técnico', 'chefe_id' => 4, 'equipe' => 0],
]);
}
protected function view(): string|array
{
return ['tutor.modulos.org-chart', []];
}
}
resources/views/tutor/modulos/org-chart.blade.php
<x-tutor-page page="modulos_org_chart">
<x-tutor-demo demo="tutor.examples.modulos-org-chart.basico"
title="Árvore básica" desc="mad-org-chart com título, subtítulo e métrica — zero PHP (fixture TutorDemoOrgFuncionario, conexão business)." />
<x-tutor-demo demo="tutor.examples.modulos-org-chart.card-customizado"
title="Card customizado" desc="mad-org-chart-card com Blade arbitrário por nó ($item, $id, $chart em escopo)." />
<x-tutor-demo demo="tutor.examples.modulos-org-chart.drag-lazy"
title="Drag re-parent + lazy-load" desc="draggable e lazy-depth exigem o HOST da página (TutorModulosOrgChart::afterReparent recalcula a métrica dos dois chefes envolvidos)."
php="TutorModulosOrgChart::afterReparent" />
</x-tutor-page>