MAD Lab
/
Tutor > Módulos visuais > Calendário

Calendário

Módulos visuais

mad-full-calendar mad-calendar-toolbar mad-calendar-popover
Agenda semanal

mad-calendar editável (drag/resize salva no banco), com toolbar custom, popover no hover e clique em evento/dia disparando métodos do controller — mad-calendar-toolbar e mad-calendar-popover juntos.

Arraste um evento para outro horário — clique num evento ou num dia vazio.
resources/views/tutor/examples/modulos-calendario/agenda.blade.php
{{--
    Reabertura do host não é necessária aqui: o compiler do calendário emite
    "MadCalendarCompiler::renderInline([...], $that ?? null)" e, se $that vier
    null (escopo isolado do componente tutor-demo), cai num
    MadCalendarStandalone que renderiza igual — os callbacks (onEventClick/
    onDayClick/onEventUpdate) são resolvidos pelo NOME do método declarado no
    config, e o wire da página sempre despacha pra o controller real
    (TutorModulosCalendario), não pro standalone usado só no render. Mesmo
    assim о padrão do lote é sempre reabrir, então mantemos por consistência.

    IMPORTANTE (achado real neste lote, mais amplo que o aviso de tag em
    comentário de linha): o compilador regex do MAD roda sobre o texto CRU
    do arquivo ANTES até da limpeza de comentário Blade (chaves + duas
    travessões) — então nem esse estilo de comentário é seguro pra citar o
    nome de uma tag entre menor-que/maior-que; ele casa a abertura falsa com
    o fechamento real mais abaixo e o componente inteiro sai com config
    vazia, SEM lançar exceção (diferente do kanban/gantt, que validam e
    falham alto). Por isso nenhuma tag é citada com os sinais de menor-que e
    maior-que em comentário nenhum deste arquivo — nem aqui, nem abaixo.
--}}
@php($that = \Mad\Component\MadRenderContext::getComponent())

<mad-calendar
    calendar-id="tutor-modulos-calendario"
    model="TutorEvento" database="business"
    title-field="titulo" start-field="inicio" end-field="fim" color-field="cor"
    default-view="agendaWeek"
    editable auto-update
    popover-title="{titulo}" popover-content="{start} → {end}">

    <mad-calendar-toolbar>
        <span style="font-size:12px; color:var(--mad-text-muted);">
            Arraste um evento para outro horário — clique num evento ou num dia vazio.
        </span>
    </mad-calendar-toolbar>

    {{-- Corpo declarado por completude de catálogo: no MadCalendarComponent
         atual, o corpo desse subelemento de popover é capturado em
         $_inlineConfig['popover'] mas NUNCA é lido/renderizado (confirmado
         lendo o source — nenhuma leitura de 'popover' fora do bloco que só
         guarda o base64). O popover que de fato aparece no hover vem dos
         atributos popover-title/popover-content acima, aplicados via
         MadFullCalendar::popover(). Ver relatório do lote. --}}
    <mad-calendar-popover>
        <strong>{titulo}</strong><br>{start} → {end}
    </mad-calendar-popover>
</mad-calendar>
Código-fonte completo da página
app/control/Tutor/TutorModulosCalendario.php
<?php

namespace App\Control\Tutor;

use App\Models\TutorDemo\TutorEvento;
use App\Support\Tutor\TutorCatalog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Mad\Calendar\MadCalendarComponent;
use Mad\Http\MadResponse;

/**
 * TutorModulosCalendario — página "Calendário" do MAD Lab (LOTE L9 / Onda 1).
 *
 * Estende Mad\Calendar\MadCalendarComponent diretamente (molde: app/control/
 * Teste/TesteCalendario.php do repositório de referência) — MadCalendarComponent
 * já É a base do módulo, então catalogKey/view() de TutorPage são
 * reimplementados aqui à mão.
 *
 * UM `<mad-calendar>` só nesta página, de propósito: a config inline
 * compilada é persistida em sessão sob a chave `<classe>_cal_cfg`
 * (MadCalendarComponent::_persistConfigToSession) — SEM distinguir por
 * calendar-id — e é essa cópia que o endpoint getEvents()/onEventUpdate
 * (chamados numa instância nova, sem o Blade do request original) usa pra
 * reconstruir o config. Um segundo `<mad-calendar>` no mesmo host
 * sobrescreveria essa sessão e quebraria os fetches/drags do primeiro.
 * Por isso os 3 conceitos do catálogo (mad-full-calendar → tag real
 * `<mad-calendar>`, mad-calendar-toolbar, mad-calendar-popover) vivem juntos
 * num único calendário — ver relatório do lote para o detalhe completo.
 */
class TutorModulosCalendario extends MadCalendarComponent
{
    protected static string $wrapper = self::INTERNAL;

    protected string $catalogKey = 'modulos_calendario';

    public function mount(array $params = []): void
    {
        $this->ensureFixture();
        parent::mount($params);
    }

    protected function view(): string|array
    {
        return TutorCatalog::page($this->catalogKey)['view'];
    }

    // ── Callbacks de clique — override dos defaults de MadCalendarComponent.
    // Sem drawer/rota nova (não temos permissão pra criar rotas neste lote):
    // os defaults abririam clickTarget/dayClickTarget (Classe::método), aqui
    // só um toast pra provar o wire-up sem precisar de um Form novo.
    // onEventUpdate (drag/resize) já vem pronto da base quando
    // `editable auto-update` está ligado — não precisa de override.

    public function onEventClick(string $id, string $title, string $view): MadResponse
    {
        return (new MadResponse())->toast("Evento: {$title} (view: {$view})", 'info');
    }

    public function onDayClick(string $date, string $view): MadResponse
    {
        return (new MadResponse())->toast("Dia clicado: {$date} (aqui abriria um form de criação)", 'info');
    }

    // ── Fixture (molde: KanbanDevView::ensureFixture) ───────────────────────

    private function ensureFixture(): void
    {
        $schema = Schema::connection('business');

        if (!$schema->hasTable('tutor_evento_eventos')) {
            $schema->create('tutor_evento_eventos', function ($t) {
                $t->increments('id');
                $t->string('titulo');
                $t->string('tipo', 32);
                $t->dateTime('inicio');
                $t->dateTime('fim');
                $t->string('cor', 16)->nullable();
            });
        }

        if (TutorEvento::query()->count() > 0) {
            return;
        }

        $tipos = [
            'reuniao' => '#3b82f6',
            'entrega' => '#16a34a',
            'visita'  => '#f59e0b',
            'revisao' => '#8b5cf6',
        ];
        $titulosPorTipo = [
            'reuniao' => ['Alinhamento semanal', 'Reunião com cliente', 'Retrospectiva'],
            'entrega' => ['Entrega do sprint', 'Deploy em produção', 'Envio do relatório'],
            'visita'  => ['Visita técnica', 'Visita ao cliente'],
            'revisao' => ['Revisão de código', 'Revisão de contrato'],
        ];

        $hoje = new \DateTimeImmutable('today');
        $tipoKeys = array_keys($tipos);
        $rows = [];

        for ($i = -3; $i <= 10; $i++) {
            $dia = $hoje->modify("{$i} days");
            // 1 a 2 eventos por dia útil, pulando alguns dias de propósito.
            if ((int) $dia->format('N') >= 6 || $i % 3 === 0) {
                continue;
            }
            $qtd = ($i % 2 === 0) ? 2 : 1;
            for ($j = 0; $j < $qtd; $j++) {
                $tipo = $tipoKeys[($i + $j + count($tipoKeys)) % count($tipoKeys)];
                $horaIni = 9 + ($j * 3);
                $inicio  = $dia->setTime($horaIni, 0);
                $fim     = $inicio->modify('+90 minutes');
                $tituloCount = count($titulosPorTipo[$tipo]);
                $titulo  = $titulosPorTipo[$tipo][(($i + $j) % $tituloCount + $tituloCount) % $tituloCount];

                $rows[] = [
                    'titulo' => $titulo,
                    'tipo'   => $tipo,
                    'inicio' => $inicio->format('Y-m-d H:i:s'),
                    'fim'    => $fim->format('Y-m-d H:i:s'),
                    'cor'    => $tipos[$tipo],
                ];
            }
        }

        DB::connection('business')->table('tutor_evento_eventos')->insert($rows);
    }
}
resources/views/tutor/modulos/calendario.blade.php
<x-tutor-page page="modulos_calendario">
    <x-tutor-demo demo="tutor.examples.modulos-calendario.agenda"
        title="Agenda semanal" desc="mad-calendar editável (drag/resize salva no banco), com toolbar custom, popover no hover e clique em evento/dia disparando métodos do controller — mad-calendar-toolbar e mad-calendar-popover juntos." />
</x-tutor-page>