Módulos visuais
mad-wf-map
Componente de leitura
<mad-wf-map> só EXIBE uma instância de workflow já existente (criada pelo
WorkflowEngine a partir de uma definição publicada no MadBuilder) — ao
contrário de mad-tree-view/mad-timeline, não tem modo estático/zero-PHP.
Os 3 exemplos abaixo usam instâncias de fixture (mesmo grafo: início →
aprovação do gerente → aprovado/rejeitado/devolvido) gravadas direto nas
tabelas mad_wf_instance/mad_wf_history só para ilustrar os estados
visuais do mapa — veja a limitação completa no código-fonte da página.
Em andamento
Nó ativo pulsando — instância aguardando decisão.
Em andamento
Pedido #1
Histórico
-
Tarefa criada — Aprovação do gerenteResponsável: perfil Gerente
-
Solicitação enviada para aprovação
resources/views/tutor/examples/modulos-wf-map/em-andamento.blade.php
@php
$that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-wf-map :instance-id="$that->instanceEmAndamentoId" />
Aprovado
Instância concluída — caminho percorrido marcado + histórico traduzido.
Aprovado
Pedido #2
Histórico
-
Fluxo finalizado — Aprovado
-
Aprovação do gerente: Aprovado“Dentro do orçamento do mês — pode seguir.”
-
Tarefa criada — Aprovação do gerenteResponsável: perfil Gerente
-
Solicitação enviada para aprovação
resources/views/tutor/examples/modulos-wf-map/aprovado.blade.php
@php
$that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-wf-map :instance-id="$that->instanceAprovadoId" />
Rejeitado
Mesmo grafo, desfecho diferente — o histórico mostra o motivo da decisão.
Rejeitado
Pedido #3
Histórico
-
Fluxo finalizado — Rejeitado
-
Aprovação do gerente: Rejeitado“Faltou nota fiscal anexada ao pedido.”
-
Tarefa criada — Aprovação do gerenteResponsável: perfil Gerente
-
Solicitação enviada para aprovação
resources/views/tutor/examples/modulos-wf-map/rejeitado.blade.php
@php
$that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-wf-map :instance-id="$that->instanceRejeitadoId" />
Código-fonte completo da página
app/control/Tutor/TutorModulosWfMap.php
<?php
namespace App\Control\Tutor;
use App\Models\TutorDemo\TutorDemoPedido;
use App\Models\Wf\History as WfHistory;
use App\Models\Wf\Instance as WfInstance;
use App\Support\Tutor\TutorDemoSeed;
use App\Support\Tutor\TutorPage;
/**
* TutorModulosWfMap — <mad-wf-map>.
*
* Ao contrário de mad-tree-view/mad-timeline, este componente NÃO tem modo
* estático/zero-PHP: ele só LÊ uma instância REAL de workflow
* (mad_wf_instance/mad_wf_task/mad_wf_history, conexão business) via
* WorkflowEngine::instanceMap() — a instância normalmente nasce de
* WorkflowEngine::start() sobre uma definição publicada no MadBuilder
* (app/Workflows/definitions/{slug}.php), algo fora do escopo do Tutor.
*
* Mínimo vivo possível: como <mad-wf-map> só LÊ (nunca decide/avança), o
* fixture grava direto 3 instâncias (mad_wf_instance + mad_wf_history) sobre
* 3 pedidos do TutorDemoSeed — sem passar pelo WorkflowEngine nem por uma
* definição arquivada. `instanceMap()` tenta resolver a definição pelo
* registry e, ao falhar (nenhum arquivo `tutor_demo_aprovacao.php` existe),
* cai no fallback `$instance->definition_snapshot` — por isso o snapshot
* (nodes + ui) já vai completo na criação da instância.
*
* LIMITAÇÃO: os botões de decisão (aprovar/rejeitar/devolver) vivem na FILA
* de tarefas — um componente diferente, fora do catálogo desta página. Aqui
* é só o mapa de leitura, então as 3 instâncias já nascem em 3 desfechos
* fixos (em andamento / aprovado / rejeitado) para ilustrar os estados
* visuais do componente.
*/
class TutorModulosWfMap extends TutorPage
{
protected string $catalogKey = 'modulos_wf_map';
private const SLUG = 'tutor_demo_aprovacao';
public int $instanceEmAndamentoId = 0;
public int $instanceAprovadoId = 0;
public int $instanceRejeitadoId = 0;
public function mount(array $params = []): void
{
$this->ensureFixture();
}
// ── Fixture ──────────────────────────────────────────────────────────
private function ensureFixture(): void
{
TutorDemoSeed::ensure();
$pedidos = TutorDemoPedido::query()->orderBy('id')->limit(3)->get();
if ($pedidos->count() < 3) {
return;
}
$this->instanceEmAndamentoId = $this->ensureInstance($pedidos[0], 'running');
$this->instanceAprovadoId = $this->ensureInstance($pedidos[1], 'approved');
$this->instanceRejeitadoId = $this->ensureInstance($pedidos[2], 'rejected');
}
private function ensureInstance(TutorDemoPedido $pedido, string $outcome): int
{
$existing = WfInstance::query()
->where('workflow_slug', self::SLUG)
->where('record_table', 'tutor_pedidos')
->where('record_id', $pedido->id)
->first();
if ($existing) {
return (int) $existing->id;
}
[$status, $nodeKey] = match ($outcome) {
'approved' => [WfInstance::STATUS_APPROVED, 'fim_aprovado'],
'rejected' => [WfInstance::STATUS_REJECTED, 'fim_rejeitado'],
default => [WfInstance::STATUS_RUNNING, 'gerente'],
};
$instance = WfInstance::create([
'workflow_slug' => self::SLUG,
'workflow_version' => 1,
'definition_snapshot' => $this->definition(),
'record_table' => 'tutor_pedidos',
'record_id' => $pedido->id,
'record_label' => $pedido->descricao,
'status' => $status,
'current_node_key' => $nodeKey,
]);
$t0 = now()->subMinutes(50);
WfHistory::create([
'instance_id' => $instance->id, 'event' => 'started',
'payload' => ['slug' => self::SLUG], 'created_at' => $t0,
]);
WfHistory::create([
'instance_id' => $instance->id, 'event' => 'task_created',
'payload' => ['node' => 'gerente', 'role' => 'gerente'],
'created_at' => $t0->copy()->addMinutes(1),
]);
if ($outcome !== 'running') {
$event = $outcome === 'approved' ? 'approved' : 'rejected';
$comment = $outcome === 'approved'
? 'Dentro do orçamento do mês — pode seguir.'
: 'Faltou nota fiscal anexada ao pedido.';
WfHistory::create([
'instance_id' => $instance->id, 'event' => $event,
'payload' => ['node' => 'gerente', 'comment' => $comment],
'created_at' => $t0->copy()->addMinutes(40),
]);
WfHistory::create([
'instance_id' => $instance->id, 'event' => 'completed',
'payload' => ['node' => $nodeKey, 'status' => $status],
'created_at' => $t0->copy()->addMinutes(41),
]);
}
return (int) $instance->id;
}
/** Grafo mínimo (início → aprovação do gerente → 3 desfechos) — só leitura no mapa. */
private function definition(): array
{
return [
'slug' => self::SLUG,
'version' => 1,
'nodes' => [
'start' => ['kind' => 'start', 'next' => 'gerente'],
'gerente' => [
'kind' => 'approval', 'name' => 'Aprovação do gerente', 'role' => 'gerente',
'on_approved' => 'fim_aprovado', 'on_rejected' => 'fim_rejeitado', 'on_returned' => 'fim_devolvido',
],
'fim_aprovado' => ['kind' => 'end', 'status' => 'approved'],
'fim_rejeitado' => ['kind' => 'end', 'status' => 'rejected'],
'fim_devolvido' => ['kind' => 'end', 'status' => 'returned'],
],
'ui' => [
'nodes' => [
'start' => ['x' => 0, 'y' => 60, 'kind' => 'start', 'name' => 'Início'],
'gerente' => ['x' => 240, 'y' => 60, 'kind' => 'approval', 'name' => 'Aprovação do gerente'],
'fim_aprovado' => ['x' => 500, 'y' => 0, 'kind' => 'end', 'name' => 'Aprovado'],
'fim_rejeitado' => ['x' => 500, 'y' => 120, 'kind' => 'end', 'name' => 'Rejeitado'],
'fim_devolvido' => ['x' => 240, 'y' => 200, 'kind' => 'end', 'name' => 'Devolvido'],
],
'edges' => [
['from' => 'start', 'to' => 'gerente'],
['from' => 'gerente', 'to' => 'fim_aprovado', 'label' => 'Aprovado'],
['from' => 'gerente', 'to' => 'fim_rejeitado', 'label' => 'Rejeitado'],
['from' => 'gerente', 'to' => 'fim_devolvido', 'label' => 'Devolvido'],
],
],
];
}
}
resources/views/tutor/modulos/wf-map.blade.php
<x-tutor-page page="modulos_wf_map">
<mad-callout type="info" title="Componente de leitura" icon="info">
<code><mad-wf-map></code> só EXIBE uma instância de workflow já existente (criada pelo
WorkflowEngine a partir de uma definição publicada no MadBuilder) — ao
contrário de mad-tree-view/mad-timeline, não tem modo estático/zero-PHP.
Os 3 exemplos abaixo usam instâncias de fixture (mesmo grafo: início →
aprovação do gerente → aprovado/rejeitado/devolvido) gravadas direto nas
tabelas mad_wf_instance/mad_wf_history só para ilustrar os estados
visuais do mapa — veja a limitação completa no código-fonte da página.
</mad-callout>
<x-tutor-demo demo="tutor.examples.modulos-wf-map.em-andamento"
title="Em andamento" desc="Nó ativo pulsando — instância aguardando decisão." />
<x-tutor-demo demo="tutor.examples.modulos-wf-map.aprovado"
title="Aprovado" desc="Instância concluída — caminho percorrido marcado + histórico traduzido." />
<x-tutor-demo demo="tutor.examples.modulos-wf-map.rejeitado"
title="Rejeitado" desc="Mesmo grafo, desfecho diferente — o histórico mostra o motivo da decisão." />
</x-tutor-page>