Docs›Componentes (Admin)›mad-gantt
Componentes (Admin)

mad-gantt

Gantt chart.

Gráfico Gantt com tarefas hierárquicas, dependências, recursos, baselines, caminho crítico e edição inline.

Spec completa (todos os atributos, sub-tags e schema low-code): ver gantt-builder.md. Esta página é um quick-start; CRUD host+form em detalhe: gantt-crud-form.md.

Três formas de uso:

API Quando usar
<mad-gantt> declarativo (Blade-first) Embedar em qualquer view sem subclass. Aceita todas as props + sub-tags
extends MadGanttComponent + <mad-gantt> no view Quando precisa de hooks PHP (callbacks, query custom, mapTask)
MadGantt::make() builder fluente Dados não-banco (memória, API externa, transformação custom)

API declarativa Blade-first

Use <mad-gantt> em qualquer view (não precisa subclass). Toda config mora em atributos + sub-tags filhas. Subclass MadGanttComponent é opcional e entra apenas para hooks comportamentais (callbacks, query custom).

Controller

<?php
use Mad\Calendar\MadGanttComponent;

class ProjetoGantt extends MadGanttComponent
{
    protected static string $wrapper = self::INTERNAL;

    protected string $model         = 'DocProjetoTarefa';
    protected string $database      = 'minierp';
    protected string $nameField     = 'titulo';
    protected string $startField    = 'dt_inicio';
    protected string $endField      = 'dt_fim';
    protected string $parentField   = 'parent_id';
    protected string $progressField = 'percentual';
    protected string $colorField    = 'cor';
    protected string $ownerField    = 'responsavel';

    // Opcional — customizar a query das tarefas (Eloquent Builder)
    protected function buildQuery(): \Illuminate\Database\Eloquent\Builder
    {
        return parent::buildQuery()->where('projeto_id', '=', $this->projetoId);
    }

    public int $projetoId = 0;
    public function mount(array $params = []): void
    {
        $this->projetoId = (int) ($params['projeto_id'] ?? 0);
    }

    protected function view(): string|array
    {
        return 'projetos.projeto-gantt';
    }
}

Blade

<mad-page-container>
    <mad-page-header title="Cronograma" icon="bar-chart-horizontal" />
    <mad-page-content>
        {!! $gantt !!}
    </mad-page-content>
</mad-page-container>

O builder $gantt é injetado automaticamente no Blade quando a subclass estende MadGanttComponent — ou use <mad-gantt model="..." /> para declarar inline.

Atributos do <mad-gantt> declarativo

<mad-gantt
    model="DocProjetoTarefa" database="minierp"
    name-field="titulo"
    start-field="dt_inicio" end-field="dt_fim"
    parent-field="parent_id"
    progress-field="percentual"
    color-field="cor"
    owner-field="responsavel"
    view-mode="months_with_day"
    zoom="month"
    start-date="2026-01-01"
    interval="90 days"
    title="Sprint Q1"
    striped-rows
    full-hours
    view-mode-btn
    zoom-btn
    on-task-click="onTaskClick"
    on-task-update="onTaskUpdate"
    on-day-click="onDayClick" />

Data source

Attr Descrição
model Classe Eloquent (model) das tarefas — habilita auto-query
database Conexão (default MAIN_DATABASE)
order-by Ordenação (default = start-field)
id-field Coluna do task id (default id)
name-field Coluna do nome (suporta dot-notation)
start-field Coluna de timestamp de início
end-field Coluna de fim
parent-field Hierarquia (vazio = flat)
progress-field Coluna de progresso — fração 0..1 ou 0-100; a leitura é decidida por progress-scale
progress-scale auto (default) | fraction (0..1) | percent (0-100) — ver abaixo
color-field Cor hex
owner-field Responsável (string ou relação dotted)
type-field task | summary | milestone
resource-field CSV de resourceIds ou nome de relação hasMany

progress-scale — normalização do progresso (5.x)

O gantt trabalha internamente com fração 0..1. Como cada banco guarda o percentual de um jeito, progress-scale declara como ler a coluna de progress-field:

Valor Leitura
auto (default) Heurística: valor > 1 é lido como 0-100 (dividido por 100); <= 1 fica como fração
fraction Sempre 0..1, sem conversão
percent Sempre 0-100, dividido por 100
<mad-gantt model="ProjetoTarefa"
    progress-field="percentual" progress-scale="percent" ... />

Banco que guarda 0-100 deve declarar percent. No auto o valor 1 é ambíguo e cai como fração — uma tarefa em 1% aparece como 100% concluída. fraction tem o espelho do problema para bancos 0..1.

A normalização acontece no ingest — na carga inicial (normalizeTasks) e também em cada task que chega depois por MadResponse (normalizeTask com o mesmo progressScale do config), então tarefa nova/atualizada segue a mesma escala das já renderizadas.

View modes / zoom

view-mode Renderiza
days (default) Coluna por dia
months Coluna por mês
days_with_hour Coluna por dia + sub-coluna por hora
months_with_day Coluna por mês + sub-coluna por dia
zoom hour | day | week | month | year — aliases legados xs→day, sm→week, md→month, lg→year. Default md (≈ month)

Display

Attr Default Descrição
interval 30 days Range visível inicial
start-date hoje Y-m-d
title '' Header acima do gantt
striped-rows true Linhas alternadas (zebra)
full-hours false Mostra todas as 24h em days_with_hour
compact-events false Reduz altura de barra de evento
locale pt-br Locale para formatação

Toolbar

Attr Descrição
view-mode-btn Botão para alternar view mode
zoom-btn Botão de zoom +/-
inline-edit Edição inline de campos
multi-select Multi-select com Ctrl+click

Dependências entre tarefas

<mad-gantt
    model="Tarefa"
    dependency-source="fk:predecessor_id" />

Opções para dependency-source:

Valor Comportamento
'' (vazio) Sem dependências
fk:nome_campo Coluna FK na própria tabela de tarefas (FS, sem lag)
table:NomeModel,from=fk_from,to=fk_to,type=tipo,lag=campo_lag Tabela separada de dependências

Recursos (resources)

<mad-gantt
    model="Tarefa"
    resource-model="Funcionario"
    resource-name-field="nome"
    resource-role-field="cargo"
    resource-capacity-field="capacidade_horas"
    show-workload
    workload-mode="hours" />

Working calendar

<mad-gantt
    working-days="1,2,3,4,5"
    working-hours="08-18">

    <mad-gantt-holiday date="2026-01-01" />
    <mad-gantt-holiday date="2026-04-21" />
    <mad-gantt-holiday date="2026-12-25" />
</mad-gantt>

Sub-tags (listas)

Use sub-tags filhas para listas (columns, filter, header actions, baselines, holidays). Detalhes completos em gantt-builder.md.

<mad-gantt model="ProjetoTarefa" name-field="titulo" start-field="dt_inicio" end-field="dt_fim">
    <mad-gantt-column field="name"     label="Tarefa" width="240" tree />
    <mad-gantt-column field="start"    label="Início" width="100" format="date" />
    <mad-gantt-column field="duration" label="Dias"   width="70"  format="days" align="center" />

    <mad-gantt-filter field="ativo" op="=" value="1" />
    <mad-gantt-filter field="deleted_at" op="is" value="null" />

    <mad-gantt-header-action label="Nova" icon="plus" method="onNova" />

    <mad-gantt-baseline task-id="42" start="2026-03-01" end="2026-03-15" />

    <mad-gantt-resource model="Funcionario" name-field="nome" role-field="cargo" />
    <mad-gantt-resource-filter field="ativo" op="=" value="1" />

    <mad-gantt-popover>
        <strong>{title}</strong><br>{start} → {end}<br>Prog: {percent}%
    </mad-gantt-popover>
</mad-gantt>

Ações reativas

Attr PHP recebe
on-task-click (string $task_id): MadResponse
on-task-update (string $task_id, string $start, string $end, string $mode = ''): MadResponse
on-day-click (string $date, ?string $row_id = null): MadResponse
on-dependency-create (string $from, string $to, string $type): MadResponse
on-dependency-delete (string $from, string $to): MadResponse
on-reload (string $startDate, string $endDate): void

Os args ligam por nome a partir do payload que o JS (mad-gantt.js) envia: {task_id} no clique, {task_id, start, end, mode} no drag/resize (mode = tipo do arraste, não progresso), {date, row_id} no clique de dia, {from, to} na dependência. O nome do parâmetro PHP tem que bater com a chave — $rowId (camelCase) nunca receberia row_id.

public function onTaskClick(string $task_id): MadResponse
{
    return MadResponse::open('TarefaForm', ['id' => $task_id]);
}

public function onTaskUpdate(string $task_id, string $start, string $end, string $mode = ''): MadResponse
{
    DB::connection('minierp')->transaction(function () use ($task_id, $start, $end) {
        $t = Tarefa::findOrFail($task_id);
        $t->dt_inicio = $start;
        $t->dt_fim    = $end;
        $t->save();
    });

    return (new MadResponse())->toast('Tarefa atualizada!', 'success');
}

API fluente (MadGantt::make())

Para dados não-banco — gera o JSON manualmente.

use Mad\Calendar\MadGantt;

class ProjetoPage extends MadComponent
{
    public function mount(): void
    {
        $this->gantt = MadGantt::make('2026-03-01')
            ->viewMode(MadGantt::MODE_DAYS)
            ->zoom('md')
            ->interval('15 days')
            ->title('Sprint 12')
            ->addRow('dev', 'Desenvolvimento')
            ->addRow('qa', 'QA')
            ->addEvent('1', 'dev', 'Feature X', '2026-03-01', '2026-03-05', '#3b82f6', 60)
            ->addEvent('2', 'qa',  'Testes',    '2026-03-06', '2026-03-08', '#10b981')
            ->onEventClick('onEventClick')
            ->render();
    }

    public function onEventClick(string $task_id): MadResponse
    {
        return MadResponse::open('TarefaForm', ['id' => $task_id]);
    }
}
{!! $that->gantt !!}

Métodos do builder

Método Uso
make(?string $startDate) Construtor
viewMode($mode) days | months | days_with_hour | months_with_day
zoom($z) hour | day | week | month | year — aliases legados xs/sm/md/lg
interval($i) Range inicial (ex: 15 days, 3 months)
title($t) Header
addRow($id, $label) Adiciona linha/recurso
addEvent($id, $rowId, $title, $start, $end, $color, $percent) Adiciona barra/tarefa (modo legado linhas+eventos)
addTask($id, array $props) Modelo hierárquico (Fase 1) — $props: name, parentId, start, end, progress, color, owner, type...
addDependency($from, $to, $type, $lag) FS/SS/FF/SF (Fase 2)
addResource($id, array $props) Recurso para workload (Fase 3) — $props: name, role, capacity, color, avatar
assignResource($taskId, $resourceId, $hours = null) Alocação de recurso
addBaseline($taskId, $start, $end) Snapshot/baseline (Fase 4)
workingDays(array $days) seg-sex = [1,2,3,4,5]
holidays(array $dates) Y-m-d
workingHours($startHour, $endHour) [8, 18]
enableAutoSchedule(bool $v = true) Reagenda automaticamente após drag
showCriticalPath(bool $v = true) Destaca caminho crítico
showWorkloadBand(bool $v = true) / workloadMode($mode) Painel de carga horária — $mode: hours | tasks | toggle
onEventClick($method) Wire click ao método PHP
onEventUpdate($method) Wire drag/resize
onDayClick($method) Wire dia vazio
render(): string Retorna HTML pronto

Exemplo end-to-end — projeto com dependências

class CronogramaProjeto extends MadGanttComponent
{
    protected static string $wrapper = self::INTERNAL;
    protected static string $title   = 'Cronograma';

    protected string $model         = 'ProjetoTarefa';
    protected string $database      = 'minierp';
    protected string $nameField     = 'titulo';
    protected string $startField    = 'dt_inicio';
    protected string $endField      = 'dt_fim';
    protected string $parentField   = 'parent_id';
    protected string $progressField = 'percentual';
    protected string $colorField    = 'cor';

    // Dependências em tabela separada
    protected string $dependencySource = 'table:ProjetoTarefaDep,from=tarefa_origem_id,to=tarefa_destino_id,type=tipo,lag=lag_dias';

    // Recursos
    protected string $resourceModel = 'Funcionario';
    protected string $resourceField = 'funcionarios';   // relação hasMany

    public int $projetoId = 0;

    public function mount(array $params = []): void
    {
        $this->projetoId = (int) ($params['projeto_id'] ?? 0);
    }

    protected function buildQuery(): \Illuminate\Database\Eloquent\Builder
    {
        return parent::buildQuery()
            ->where('projeto_id', '=', $this->projetoId)
            ->orderBy('dt_inicio');
    }

    public function onTaskClick(string $task_id): MadResponse
    {
        return MadResponse::open('ProjetoTarefaForm', ['id' => $task_id]);
    }

    // ATENCAO: o parametro TEM que se chamar $task_id — e a chave do payload
    // do drag. Um $id (nome antigo) nunca resolve e o handler recebe vazio.
    public function onTaskUpdate(string $task_id, string $start, string $end, $progress = null): MadResponse
    {
        DB::connection('minierp')->transaction(function () use ($task_id, $start, $end, $progress) {
            $t = ProjetoTarefa::findOrFail($task_id);
            $t->dt_inicio = $start;
            $t->dt_fim    = $end;
            if ($progress !== null) {
                $t->percentual = (float) $progress;
            }
            $t->save();
        });

        return (new MadResponse())->toast('Salvo!', 'success');
    }

    protected function view(): string|array
    {
        return 'projetos.cronograma-projeto';
    }
}
<mad-page-container>
    <mad-page-header :title="'Cronograma — Projeto #' . $that->projetoId" icon="bar-chart-horizontal">
        <actions>
            <mad-btn navigate="ProjetoTarefaForm" variant="primary" icon="plus">Nova tarefa</mad-btn>
        </actions>
    </mad-page-header>

    <mad-page-content>
        {!! $gantt !!}
    </mad-page-content>
</mad-page-container>

Gotchas

  • $nameField, $startField, $endField obrigatórios em MadGanttComponent — sem eles a query falha silenciosamente
  • Datas no formato Y-m-d H:i:s (DB-format) — o JS faz parsing nativo
  • parent-field exige hierarquia coerente — child com parent_id inexistente é renderizado flat (sem warning)
  • Dependências em dependency-source="fk:..." assumem tipo FS (Finish-to-Start) sem lag — para outros tipos, use table:
  • Workload mode = 'hours' exige addAssignment com horas concretas — sem isso, mostra workload zero

Doc completa: ler lib/mad/calendar/MadGantt.php (builder) e lib/mad/calendar/MadGanttComponent.php (componente declarativo).