mad-org-chart
Organograma de hierarquia auto-referente com pan/zoom, collapse, busca, lazy-load e drag re-parent.
Organograma: renderiza uma hierarquia auto-referente (FK do tipo parent_id)
como arvore de cards com pan, zoom, collapse, busca, lazy-load por nivel e
drag para re-parent. Uma tag declarativa, com filho opcional para o template do
card.
Compilador: Mad\OrgChart\MadOrgChartCompiler (passo 1.10 do MadBlade).
Componente: Mad\OrgChart\MadOrgChart. Sem host proprio, o compiler instancia
um Mad\OrgChart\MadOrgChartStandalone. Client: Alpine madOrgChart
(mad-orgchart.js + mad-orgchart.css).
Tag
<mad-org-chart model="Employee" database="business"
parent-field="manager_id"
title="nome" subtitle="cargo" avatar-field="foto"
metric="equipe_count" metric-label="equipe"
order-field="nome"
lazy-depth="2" max-nodes="1000"
click-target="EmployeeForm::onEdit({id})"
where="ativo=1" draggable />
Props de <mad-org-chart>
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
model |
string | — | Model dos nos. Obrigatorio |
database |
string | MAIN_DATABASE |
Conexao |
parent-field |
string | parent_id |
FK auto-referente |
title |
string | nome |
Campo do titulo do card |
subtitle |
string | '' |
Campo do subtitulo |
avatar-field |
string | '' |
Campo da foto. Sem ele o card usa as iniciais |
metric |
string | '' |
Campo numerico exibido no badge |
metric-label |
string | '' |
Rotulo da metrica |
order-field |
string | '' |
Ordenacao dos irmaos |
draggable |
bool | false |
Habilita drag para re-parent |
lazy-depth |
int | 0 |
Niveis renderizados de inicio (0 = arvore toda) |
max-nodes |
int | 1000 |
Guard de nos por request |
root-id |
int | 0 |
Restringe a arvore a um no raiz |
click-target |
string | '' |
Abre um form ao clicar no card, ex. EmployeeForm::onEdit({id}) |
where |
string | '' |
Filtro DSL (ativo=1|tipo=P) ou :where="$closure" |
<mad-org-chart-card>
Filho opcional com o template Blade do card. O corpo e arbitrario e recebe
$item, $id e $chart no escopo.
<mad-org-chart model="Employee" title="nome" subtitle="cargo">
<mad-org-chart-card>
<strong>{!! e($item['nome']) !!}</strong>
<small>#{!! $id !!}</small>
</mad-org-chart-card>
</mad-org-chart>
Avulso ou pagina propria
A tag funciona embutida em qualquer blade (varias na mesma pagina, inclusive). O que muda e a disponibilidade das acoes wire:
| Cenario | Funciona avulso? |
|---|---|
Arvore completa sem drag (pan, zoom, collapse, busca, click-target — tudo client-side) |
Sim, em qualquer pagina/host |
lazy-depth > 0 (expandir busca filhos via onLoadChildren) |
Nao: o host da pagina precisa ser subclasse de MadOrgChart |
draggable (re-parent via onReparent) |
Nao: idem |
Regra pratica: visualizacao readonly que cabe no max-nodes vira widget
embutido; ligou lazy ou drag (ou a hierarquia estoura o max-nodes) entao gere
subclasse e pagina propria — mesma regra do kanban e do gantt embutidos.
Comportamento no client
- Pan arrastando o fundo do viewport; zoom por scroll ancorado no cursor (30% a 200%) e botoes -/+/Resetar na toolbar.
- Collapse pelo badge de contagem na base do card com filhos.
- Lazy-load: no alem do
lazy-depthmostra o badge com a contagem pendente; o clique busca os filhos no servidor (onLoadChildren, ophtml) e o eventomad-orgchart:childrenlimpa o spinner. - Busca filtra por titulo e subtitulo, destaca os hits, expande ancestrais colapsados e centraliza o primeiro.
- Drag re-parent (com
draggable): o servidor valida e a arvore re-renderiza inteira. - Titulo e subtitulo saem escapados.
A carga e BFS por nivel: uma query por profundidade (whereIn nos pais) mais
uma query agregada de contagem de filhos — sem N+1 por no.
Subclasse (hooks)
use Mad\OrgChart\MadOrgChart;
class OrganogramaRh extends MadOrgChart
{
protected static string $wrapper = self::INTERNAL;
protected string $model = 'Employee';
protected string $database = 'business';
protected string $parentField = 'manager_id';
protected string $titleField = 'nome';
protected string $subtitleField = 'cargo';
protected bool $draggable = true;
protected int $lazyDepth = 2;
/** Escopo aplicado em TODO acesso a no (anti-IDOR). */
protected function query(\Illuminate\Database\Eloquent\Builder $q): void
{
$q->where('unit_id', MadSession::unitId())->where('ativo', 1);
}
protected function canReparent(object $node, ?object $newParent): bool
{
return $this->draggable;
}
protected function afterReparent(object $node, int $oldParentId, int $newParentId): void
{
}
}
Gotchas
- Sem
query($q)o organograma enxerga todo mundo: e ele que fecha o IDOR doonLoadChildrene doonReparent. onReparentvalida em camadas:draggableligado, depoiscanReparent(), depois cycle-check (sobe do novo pai ate a raiz; se passar pelo no movido, recusa "destino e descendente"), so entao persiste e forca full render.parent-fieldeorder-fieldsao validados como identificadores SQL.- Estourar
max-nodestrunca a carga (isTruncated()); nesse caso uselazy-depthouroot-idem vez de aumentar o guard sem criterio. - Atributo
mad-*nao aceita{{ }}— expressao PHP entra com dois-pontos (:where="$filtro").