Docs›Componentes (Admin)›mad-full-calendar
Componentes (Admin)

mad-full-calendar

Calendário FullCalendar.io.

Componente declarativo de calendário baseado em FullCalendar v5.5.1, com API Blade-first. Toda configuração mora em atributos da tag <mad-calendar>. A classe PHP é uma casca (extends MadCalendarComponent) — adiciona código apenas para hooks de comportamento custom.

Princípio: controller é casca. Quanto mais atributos no Blade, menos PHP. Pensado para geradores de código visuais (low-code).

Quick start

Controller mínimo (sem hooks)

<?php
use Mad\Calendar\MadCalendarComponent;

class AgendaPage extends MadCalendarComponent
{
    // Vazio. Toda config no Blade.
}

View

<mad-page-container>
    <mad-page-header title="Agenda" icon="calendar" />
    <mad-page-content>
        <mad-calendar
            model="Agendamento" database="minierp"
            title-field="titulo"
            start-field="dt_inicio" end-field="dt_fim"
            color-field="cor"
            default-view="agendaWeek"
            time-range="07:00-19:00"
            no-weekend
            editable auto-update
            click-target="AgendamentoForm::onEdit({id})"
            click-target-mode="drawer"
            day-click-target="AgendamentoForm::onCreate({date})"
            period-type="date-range" date-field="dt_inicio" remember-filters>

            <mad-calendar-filter field="ativo" op="=" value="1" />
            <mad-calendar-filter field="deleted_at" op="is" value="null" />
        </mad-calendar>
    </mad-page-content>
</mad-page-container>

Atributos do <mad-calendar>

Data source

Attr Descrição
model Classe model Eloquent — habilita auto-query
database Conexão (default MAIN_DATABASE)
id-field Coluna do event id (default id)
title-field Coluna do título (suporta dot-notation: cliente.nome)
start-field Coluna de timestamp de início
end-field Coluna de fim (opcional)
color-field Coluna com hex color
color Cor literal fallback (#3b82f6)
color-map {"field":"tipo","map":{"A":"#3b82f6","B":"#f59e0b"}}
resource-field Coluna FK pro recurso. Auto-mapeada como resourceId no JSON (resource timeline)
all-day-field Coluna booleana → allDay no JSON (evento dia inteiro)
editable-field Coluna booleana → editable per-event (override do drag/resize por linha)
extra-fields CSV de colunas extras no payload do evento
events-url Escape hatch — URL custom (bypassa auto-query)
order-by Ordenação default

View / período

Attr Default Descrição
default-view dayGridMonth month | agendaWeek | agendaDay | listWeek
time-range 00:00-24:00 HH:MM-HH:MM
enable-days 0,1,2,3,4,5,6 Dias visíveis (0=Dom..6=Sab)
no-weekend false Atalho: enable-days=1,2,3,4,5
slot-duration 01:00 Slot do agenda view
num-days 4 Dias visíveis em multi-day view
locale pt-br Locale FullCalendar
current-date hoje Y-m-d inicial

Interação

Attr Descrição
editable Habilita drag + resize
no-dragging Desabilita arrastar (mantém resize, se editable)
no-resizing Desabilita redimensionar (mantém drag, se editable)
auto-update Default true — grava store() automático no drag
confirm-update Mensagem de confirm() antes do save
click-target Classe::metodo({id}) — abre form no event-click
click-target-mode drawer | modal
day-click-target Classe::metodo({date}) — clique em dia vazio
event-update-method Nome do método PHP custom (default onEventUpdate)
event-click-method Override método PHP do click
day-click-method Override método PHP do day click

Filtros (MadFiltersTrait integrado)

Attr Descrição
period-type none | month-year | date-range | preset
date-field Coluna usada pelo filtro de período
remember-filters Persiste filtros em sessão
default-current-period Seed mes/ano com data atual
apply-unit-filter Multi-tenant

Display

Attr Descrição
calendar-id DOM id (obrigatório para refetchCalendar)
height Px (0 = auto)
full-height Altura automática (preenche container)
popover-title Template com {title} {start} {end} {extra_*}
popover-content Idem
popover-trigger hover (default) | click — como o popover abre
extra-options JSON pass-through para FullCalendar (slotDuration, nowIndicator, etc)

Sub-tags

Sub-tag Tipo Descrição
<mad-calendar-toolbar> block Body Blade arbitrário renderizado acima do calendário
<mad-calendar-popover> block Body do popover (HTML rico). Alternativa aos attrs popover-*
<mad-calendar-resource> single Ativa resource-timeline. Attrs: model, title-field, color-field, label, etc
<mad-calendar-filter> repeat Filtro fixo nos eventos. Attrs: field, op, value, value2
<mad-calendar-resource-filter> repeat Filtros fixos para a query de recursos
<mad-calendar-filters> block Filtros user-facing (MadFiltersTrait). Ver grid-filters

Exemplo: toolbar + popover + filtro fixo

<mad-calendar
    model="Agendamento"
    title-field="titulo"
    start-field="dt_inicio"
    extra-fields="cliente.nome,tipo"
    popover-title="{titulo}"
    popover-content="{cliente_nome} — {start} → {end}">

    <mad-calendar-toolbar>
        <mad-btn navigate="AgendamentoForm" variant="primary" icon="plus">Novo</mad-btn>
        <mad-btn mad:click="onReload" variant="ghost" icon="refresh-cw">Recarregar</mad-btn>
    </mad-calendar-toolbar>

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

Exemplo: resource timeline (linhas=recursos, colunas=tempo)

<mad-calendar
    calendar-id="cal-salas"
    model="Reserva"
    title-field="titulo"
    start-field="dt_inicio" end-field="dt_fim"
    resource-field="sala_id"
    default-view="resourceTimelineDay"
    time-range="08:00-18:00"
    slot-duration="01:00"
    editable auto-update
    slot-click-target="ReservaForm::onCreate({date},{resourceId})"
    click-target="ReservaForm::onEdit({id})">

    <mad-calendar-resource model="Sala" title-field="nome" color-field="cor" label="Sala" />
</mad-calendar>

Guia completo: ver seção Resource Timeline abaixo.

Hooks PHP

Hook Quando
onSearch(Builder $q): void Filtros adicionais — builder-native, aplica $q->where(...) direto (mesmo contrato do Kanban/Grid/Dashboard)
buildQuery(): Builder Override total do builder de eventos (bypassa model + filtros automáticos)
mapEvent(object $record): array Composição custom de título/cor/payload
loadEvents(string $start, string $end): array Override total da fonte de eventos
loadResources(): array Recursos hardcoded (sem precisar de tabela de recursos no banco)
beforeEventUpdate($id, $start, $end): bool Retornar false cancela o auto-update
afterEventUpdate($id, $os, $oe, $ns, $ne): void Pós-store, ideal para auditoria/log
onEventClick($id, $title, $view): MadResponse Override total do click (bypassa click-target)
onDayClick($date, $view): MadResponse Override total do day click
onSlotClick($date, $resId, $resTitle): MadResponse Resource-timeline slot click
configureCalendar(MadFullCalendar $cal): void Hook raw — chamar ->option() não expostos via attr

Exemplo: hooks de query e mapeamento

use Illuminate\Database\Eloquent\Builder;

class AgendaCliente extends MadCalendarComponent
{
    public int $clienteId = 0;

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

    // Filtro fixo derivado de prop pública — builder-native
    public function onSearch(Builder $q): void
    {
        $q->where('cliente_id', '=', $this->clienteId);
    }

    // Mapeamento custom (título concatenado, cor de relação)
    protected function mapEvent(object $r): array
    {
        return [
            'id'    => (string) $r->id,
            'title' => ($r->tipo->nome ?? '') . ' — ' . $r->descricao,
            'start' => $r->dt_inicio,
            'end'   => $r->dt_fim,
            'color' => $r->tipo->cor ?? '#3b82f6',
        ];
    }

    // Auditoria pós-drag/resize (auto-update grava + este hook só loga)
    protected function afterEventUpdate(int $id, string $os, string $oe, string $ns, string $ne): void
    {
        AgendamentoHistorico::create([
            'agendamento_id' => $id,
            'de'             => $os,
            'para'           => $ns,
        ]);
    }
}

Filtros declarativos — <mad-calendar-filters>

Mesma API de <mad-grid-filters>. Ver filtros declarativos.

<mad-calendar-filters style="toolbar">
    <mad-dbcombo-field name="cliente_id" label="Cliente" model="Pessoa" display="nome" />
    <mad-select-field name="prioridade" label="Prioridade" :items="['1'=>'Alta','2'=>'Média']" />
    <mad-period-monthyear />
</mad-calendar-filters>

<mad-calendar model="Evento" ... />

Controller:

class AgendaPage extends MadCalendarComponent
{
    protected string $periodType = 'month-year';

    public string $cliente_id = '';   // auto-discovery
    public string $prioridade = '';
}

Atualizar dados sem reload

Após salvar/criar/deletar em form:

public function onSave(): MadResponse
{
    // ... salvar evento ...
    return (new MadResponse())
        ->toast('Salvo!', 'success')
        ->closeDrawer()
        ->refetchCalendar('cal-agenda');   // ID do <mad-calendar calendar-id="cal-agenda">
}

Atributos do <mad-calendar-resource>

Attr Descrição
model Classe model Eloquent do recurso
database Conexão do recurso (default: a mesma do <mad-calendar>)
id-field Default id
title-field Coluna do nome
color-field Coluna com hex
label Label visível no header (ex: "Sala", "Funcionário")
order-by Ordenação
slot-duration Override do slot-duration herdado do <mad-calendar>
num-days Override do num-days herdado do <mad-calendar>
slot-click-target Pode ser declarado aqui em vez de no <mad-calendar> pai

Resource Timeline — guia completo

Resource Timeline = linhas são recursos (salas, equipamentos, pessoas), colunas são slots de tempo. É renderizado por uma grade CSS/Alpine própria do MAD (madResourceTimeline), não pelo plugin resourceTimeline do FullCalendar (que não está no bundle). Diferente das views de mês/semana padrão, cada evento precisa apontar pra um recurso via FK.

Estrutura mínima do banco

Tabela de recursos (linhas da timeline):

CREATE TABLE sala (
    id     SERIAL PRIMARY KEY,
    nome   VARCHAR(100) NOT NULL,
    cor    VARCHAR(7)              -- opcional, hex #3b82f6
);
Coluna Obrigatória Mapeada via Notas
id (PK) sim id-field no <mad-calendar-resource> int / varchar / uuid
nome sim title-field texto exibido na linha
cor opcional color-field hex #3b82f6

Tabela de eventos (barras na timeline):

CREATE TABLE reserva (
    id          SERIAL PRIMARY KEY,
    sala_id     INT NOT NULL REFERENCES sala(id),   -- FK obrigatória
    titulo      VARCHAR(200) NOT NULL,
    dt_inicio   TIMESTAMP NOT NULL,
    dt_fim      TIMESTAMP NOT NULL,
    cor         VARCHAR(7),
    dia_inteiro CHAR(1) DEFAULT 'N'                 -- opcional (all-day-field)
);
Coluna Obrigatória Mapeada via Notas
id sim id-field
titulo sim title-field
dt_inicio sim start-field datetime
dt_fim sim p/ timeline end-field sem isso, evento vira ponto
sala_id sim resource-field chave do resource timeline — sem ela, evento não aparece na linha
cor opcional color-field
dia_inteiro opcional all-day-field bool — vira allDay no JSON

Setup completo

class ReservaCalendar extends MadCalendarComponent
{
    // Casca — toda config no Blade
}
<mad-calendar
    calendar-id="cal-salas"
    model="Reserva" database="minierp"
    title-field="titulo"
    start-field="dt_inicio" end-field="dt_fim"
    resource-field="sala_id"
    color-field="cor"
    default-view="resourceTimelineDay"
    time-range="08:00-18:00" slot-duration="01:00" num-days="5"
    editable auto-update
    slot-click-target="ReservaForm::onCreate({date},{resourceId})"
    click-target="ReservaForm::onEdit({id})">

    <mad-calendar-resource
        model="Sala"
        title-field="nome"
        color-field="cor"
        label="Sala"
        order-by="nome" />

    <mad-calendar-filter field="deleted_at" op="is" value="null" />
</mad-calendar>

Como resource-field funciona

Sem o atributo, mapEvent() emite o JSON sem resourceId — a grade de resource timeline (grid Alpine próprio do MAD, madResourceTimeline, não um plugin do FullCalendar) não sabe em qual linha colocar o evento e ele some.

Com resource-field="sala_id":

  1. mapEvent() lê $record->sala_id
  2. Emite {"resourceId": "<valor>", ...} no JSON
  3. FullCalendar plota o evento na linha do recurso correspondente

Suporta dot-notation pra navegação por relação:

resource-field="reserva.sala_id"

Views suportadas

default-view Layout
resourceTimelineDay 1 dia, horas como colunas
resourceTimelineWeek 7 dias, dias como colunas
resourceTimeline (multi-day) N dias via num-days="N"

Recursos hardcoded (sem tabela)

Override loadResources() quando recursos são fixos (turnos, equipes pequenas):

class TurnoCalendar extends MadCalendarComponent
{
    protected function loadResources(): array
    {
        return [
            ['id' => 'manha',  'title' => 'Manhã',  'color' => '#3b82f6'],
            ['id' => 'tarde',  'title' => 'Tarde',  'color' => '#f59e0b'],
            ['id' => 'noite',  'title' => 'Noite',  'color' => '#6366f1'],
        ];
    }
}
<mad-calendar
    model="Plantao"
    title-field="funcionario.nome"
    start-field="dt_inicio" end-field="dt_fim"
    resource-field="turno"
    default-view="resourceTimelineDay" />

Sem <mad-calendar-resource> — loadResources() ganha precedência.

Filtros nos recursos

Pra restringir quais recursos aparecem na timeline (ex: só salas ativas):

<mad-calendar ...>
    <mad-calendar-resource model="Sala" title-field="nome" />
    <mad-calendar-resource-filter field="ativo" op="=" value="1" />
    <mad-calendar-resource-filter field="deleted_at" op="is" value="null" />
</mad-calendar>

Slot click → criar evento já vinculado ao recurso

slot-click-target recebe {date} e {resourceId} automaticamente:

slot-click-target="ReservaForm::onCreate({date},{resourceId})"

Controller:

public function onCreate(string $date, string $resourceId): void
{
    $this->form->set('dt_inicio', $date);
    $this->form->set('sala_id',   $resourceId);   // pré-seleciona o recurso clicado
}

Eventos dia-inteiro — all-day-field

Coluna booleana no model marca o evento como dia inteiro (renderiza no topo, sem horário):

ALTER TABLE reserva ADD COLUMN dia_inteiro CHAR(1) DEFAULT 'N';
<mad-calendar
    model="Reserva"
    title-field="titulo"
    start-field="dt_inicio" end-field="dt_fim"
    all-day-field="dia_inteiro"
    default-view="agendaWeek" />

_truthy() aceita 1/0, t/f, S/N, true/false, yes/no. Qualquer valor não-zero/não-vazio = true.

Edição per-event — editable-field

Trava drag/resize linha-a-linha. Útil pra status do tipo "aprovado/finalizado" que não devem mais ser movidos:

ALTER TABLE reserva ADD COLUMN permite_editar CHAR(1) DEFAULT 'S';
<mad-calendar
    model="Reserva"
    title-field="titulo"
    start-field="dt_inicio" end-field="dt_fim"
    editable auto-update
    editable-field="permite_editar" />

Lógica:

  • editable no <mad-calendar> = default global (true/false)
  • editable-field = override per-event vindo do banco
  • Evento com permite_editar = 'N' → não arrasta nem redimensiona

Combina bem com regras de negócio via accessor Eloquent (Attribute::make()) — o _resolvePath() lê $record->permite_editar normalmente, e o Eloquent resolve via accessor mesmo sem coluna real na tabela:

use Illuminate\Database\Eloquent\Casts\Attribute;

class Reserva extends Model
{
    protected function permiteEditar(): Attribute
    {
        return Attribute::make(
            get: fn () => in_array($this->status, ['rascunho', 'pendente'], true) ? 'S' : 'N',
        );
    }
}
editable-field="permite_editar"

Gotchas

  • title-field e start-field obrigatórios — sem eles a query auto não monta
  • Dot-notation em title-field funciona (cliente.nome) — navega relações Eloquent via _resolvePath() (suporta belongsTo/hasOne)
  • auto-update chama $record->save() direto — se precisa de validação extra, override beforeEventUpdate ou onEventUpdate
  • refetchCalendar('id') requer calendar-id="id" — sem isso, o JS não acha o DOM
  • extra-fields lista colunas necessárias para os popover/click targets — campos não listados não chegam ao client
  • Resource timeline sem resource-field — eventos somem (a grade própria madResourceTimeline não sabe em qual linha plotar). Sempre declarar a coluna FK
  • all-day-field / editable-field aceitam qualquer truthy — 1/0, S/N, t/f, true/false (case-insensitive) — usa _truthy()

Fonte: packages/mad-framework/src/mad/calendar/MadCalendarComponent.php (componente) e MadFullCalendar.php (builder FullCalendar). Resource timeline em detalhe: full-calendar-resource.md.