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>