MAD Lab
/
Tutor > Módulos visuais > Mapa de fluxo (wf-map)

Mapa de fluxo (wf-map)

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
Aprovado Rejeitado Devolvido Início início Aprovação do gerente aprovação Aprovado fim Rejeitado fim Devolvido fim
Histórico
  • Tarefa criada — Aprovação do gerente
    Responsá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
Aprovado Rejeitado Devolvido Início início Aprovação do gerente aprovação Aprovado fim Rejeitado fim Devolvido fim
Histórico
  • Fluxo finalizado — Aprovado
  • Aprovação do gerente: Aprovado
    “Dentro do orçamento do mês — pode seguir.”
  • Tarefa criada — Aprovação do gerente
    Responsá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
Aprovado Rejeitado Devolvido Início início Aprovação do gerente aprovação Aprovado fim Rejeitado fim Devolvido fim
Histórico
  • Fluxo finalizado — Rejeitado
  • Aprovação do gerente: Rejeitado
    “Faltou nota fiscal anexada ao pedido.”
  • Tarefa criada — Aprovação do gerente
    Responsá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>&lt;mad-wf-map&gt;</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>