📄 Documento de Execução — Card 7c: Calendário Interativo da Agenda
| Campo | Valor |
|---|---|
| Card Trello | [M1] Calendário Interativo da Agenda do Aluno |
| Marco | M1 — Portal do Aluno (STUDENT) |
| Data execução | 28/07/2026 |
| Responsável | Wellington Santiago (via ZCode) |
| Doc anterior | Card 7b: Motor de Gamificação |
| Próximo card | Card 8: Home da Família (M2) |
| Commit | 2415003 |
🎯 1. O que foi implementado
Página /aluno/agenda com calendário mensal interativo que agrega 4 fontes de evento em uma
visão unificada. O aluno vê tudo que professores e direção definiram: prazos de tarefas, provas,
eventos gerais da escola (feriados, reuniões, eventos sociais) e comunicados da turma. Tudo
somente leitura — o aluno não cria eventos (escopo de criação fica para LOCAL/MASTER no futuro).
Os 4 tipos de evento agregados
| Tipo | Fonte (schema) | Cor no calendário | Origem |
|---|---|---|---|
| 🟡 Tarefa | Task.dueDate |
âmbar (--warning-500) |
Professores criam tarefas |
| 🔴 Prova | Exam.availableFrom |
vermelho (--danger-500) |
Professores criam provas |
| 🔵 Evento | CalendarEvent |
azul (--accent-500) |
Direção (LOCAL/MASTER) |
| 🟢 Comunicado | Announcement.publishedAt |
verde (--success-500) |
Direção/Professores |
Componentes da feature
Procedure tRPC
student.calendar.month(src/server/trpc/routers/student.ts):- Input:
{ year, month }(1-12). Calcula range[1º dia do mês, último dia]. - Resolve
student.branchId+courseIds das matrículas ACTIVE do aluno. - Roda 4 queries em paralelo (Promise.all) dentro do range:
- Tasks:
dueDate in range, status PUBLISHED, subject.courseId in matrículas - Exams:
availableFrom in range, isPublished, subject.courseId in matrículas - CalendarEvents:
startDate in range, OR: [branchId do aluno, branchId null (global)] - Announcements:
publishedAt in range, status PUBLISHED, filtro branch, targetRoles inclui STUDENT
- Tasks:
- Filtra announcements que NÃO são para STUDENT (targetRoles pode ser null = público).
- Normaliza tudo em
CalendarItem[]comtype,title,dateKey(YYYY-MM-DD),meta(subject, time, isAllDay) ehref(link para detalhe da tarefa). - Retorna
{ items, byDateKey: Record<dateKey, CalendarItem[]> }agrupado por dia.
- Input:
Componente
Calendarestendido (src/components/ui/Calendar.tsx):- Antes:
markers?: Record<string, string>(1 ponto por dia). - Agora:
markers?: Record<string, string | string[]>(até 4 pontos coloridos por dia, um por tipo). Mantém compatibilidade (string vira[string]). - Acessibilidade: aria-label agora inclui contagem ("3 evento(s)").
- Antes:
Página
/aluno/agenda(src/app/(dashboard)/aluno/agenda/page.tsx):- Layout grid 2 colunas (desktop) / empilhado (mobile):
- Esquerda:
<Calendar>navegável + botão "Hoje" + lenda dos 4 tipos. - Direita: painel "Eventos do dia" com lista de
CalendarItems do dia selecionado.
- Esquerda:
- Cada item no painel: ícone (Lucide por tipo) + borda esquerda colorida + título + meta (disciplina, horário) + descrição + link "Ver tarefa →" se aplicável.
- Estado vazio: "Nenhum evento neste dia."
- Suspense + ErrorBoundary +
.Skeletonestático (padrão das outras páginas). - Trocar de mês no Calendar dispara nova query
student.calendar.monthautomaticamente.
- Layout grid 2 colunas (desktop) / empilhado (mobile):
Navegação corrigida + botão novo:
StudentShell: item "Calendário" do top-nav apontava para/aluno/frequencia(BUG desde o Card 4) → agora aponta para/aluno/agenda./aluno/tarefas: botão "Ver agenda" no header (link para/aluno/agenda)./aluno/frequenciacontinua acessível pelo bottom-nav mobile ("Frequência").
i18n: seção
student.calendar(12 chaves: title, today, eventsOfDay, noEvents, legendTask/Exam/Event/Announcement, allDay, viewTask, loading, error) +viewAgendaemstudent.tasks. PT/EN/ES.
Bug corrigido durante o card
StudentShelltop-nav: itemt('calendar')apontava para/aluno/frequenciadesde o Card 4 (placeholder que nunca foi corrigido). Agora/aluno/agenda.
✅ Critérios de aceite validados
| # | Critério | Status |
|---|---|---|
| 1 | Página /aluno/agenda carrega com calendário mensal navegável |
✅ |
| 2 | Dias com eventos mostram pontinhos coloridos por tipo (até 4) | ✅ |
| 3 | Clicar num dia mostra a lista de eventos daquele dia | ✅ |
| 4 | Trocar de mês busca os eventos do novo mês | ✅ |
| 5 | 4 fontes agregadas (tarefas, provas, eventos, comunicados) | ✅ |
| 6 | Filtragem por branch do aluno (global + da unidade) | ✅ |
| 7 | Item "Calendário" do menu aponta para /aluno/agenda (bug corrigido) |
✅ |
| 8 | Botão "Ver agenda" na página de tarefas | ✅ |
| 9 | i18n PT/EN/ES | ✅ |
| 10 | Build passa + lint OK | ✅ |
| 11 | Deploy DEV funcional | ✅ |
Validação real da procedure (autenticada, julho/2026)
Itens no mês: 5 | Dias com eventos: 4
Por tipo: { EVENT: 4, ANNOUNCEMENT: 1 }
[EVENT] 2026-07-08 - Reunião de Pais e Mestres
[EVENT] 2026-07-15 - Feriado Nacional — Sem aulas
[EVENT] 2026-07-22 - Mostra de Arte dos Alunos
[EVENT] 2026-07-28 - Prazo final — Entregas do Bimestre
[ANNOUNCEMENT] 2026-07-28 - Reunião de pais e mestres
📁 2. Arquivos criados/modificados
Criados
| Arquivo | Função |
|---|---|
src/app/(dashboard)/aluno/agenda/page.tsx |
Página da agenda (calendário + painel dia + lenda) |
scripts/seed_agenda_demo.js |
Seed: 4 CalendarEvents demo (reunião, feriado global, mostra de arte, prazo) |
Modificados
| Arquivo | Mudança |
|---|---|
src/server/trpc/routers/student.ts |
+sub-router calendar com procedure month (agrega 4 fontes) |
src/components/ui/Calendar.tsx |
markers agora aceita string | string[]; renderiza até 4 pontos por dia |
src/components/layout/StudentShell.tsx |
Corrigido bug: item "Calendário" → /aluno/agenda (antes /frequencia) |
src/app/(dashboard)/aluno/tarefas/page.tsx |
+botão "Ver agenda" no header |
src/styles/tarefas-page.module.css |
+classe .agendaLink |
messages/{pt-BR,en-US,es-ES}.json |
+seção student.calendar (12 chaves) + student.tasks.viewAgenda |
🗄️ 3. Schema do banco (mudanças)
Nenhuma mudança de schema. A feature reusa 4 modelos já existentes:
Task.dueDate(DateTime?) — prazo de entregaExam.availableFrom(DateTime?) — abertura da provaCalendarEvent(startDate, endDate, isAllDay, type, branchId) — eventos geraisAnnouncement(publishedAt, targetRoles, branchId) — comunicados
ℹ️
CalendarEvent.typecontinuaString?(livre). O seed usa valores comoREUNIAO,FERIADO,EVENTO_SOCIAL,PRAZO. Se no futuro quiser padronizar cores portype, introduzir um enumCalendarEventType(sugestão para o Card 21).
🔌 4. Handoff para o próximo card
API pública
student.calendar.month({ year, month })→{ items: CalendarItem[], byDateKey: Record<string, CalendarItem[]> }CalendarItem.type:'TASK' | 'EXAM' | 'EVENT' | 'ANNOUNCEMENT'CalendarItem.dateKey:YYYY-MM-DD(chave para agrupar no calendário)CalendarItem.href: link para detalhe (ex:/aluno/tarefas/{id})
Componente Calendar reutilizável
<Calendar value={date} onChange={fn} markers={Record<string, string[]>} initialMonth={date} />markerséRecord<YYYY-MM-DD, string[]>onde cada string é uma cor CSS var.- Cap de 4 pontos por dia (visual). Mais que 4 tipos: trunca.
Triggers para próximos cards
- Card 21 (Comunicados & Avisos da Unidade): quando LOCAL/MASTER cria
AnnouncementouCalendarEvent, eles aparecem automaticamente no calendário do aluno (sem código extra). Criar mutations CRUD de CalendarEvent + Announcement lá. - Card 12/13 (Diário/Chamada): quando professor publica
TaskouExam, eles aparecem automaticamente no calendário dos alunos matriculados (filtro por courseId). - Card 9 (Acompanhamento PARENT): pais veem agenda dos filhos — reusar
student.calendar.monthcomstudentUserIddo Student child (ajustar procedure).
Lições aprendidas
- Agregação cross-model no procedure: em vez de N endpoints, uma procedure que junta
múltiplas tabelas em um shape comum (
CalendarItem) simplifica muito o frontend. O custo de 4 queries paralelas (Promise.all) é aceitável para um calendário mensal. - Componente Calendar extensível: o
markerscomoRecord<string, string[]>permite múltiplas cores por dia sem mudar a API para quem já usavastring. - Login do NextAuth usa campo
login(nãoemail): aceita email OU matrícula. Testes de auth devem usarlogin=.... Anotado para sessões futuras.
📋 5. Deploy DEV
- URL: https://sistemaescolar.wellka.com.br/aluno/agenda
- Login demo:
aluno@genioon.com.br/Aluno@2026 - Commit:
2415003 - PM2: genioon-dev restart ✓ (online)
- Seed:
node scripts/seed_agenda_demo.jscriou 4 CalendarEvents:- Reunião de Pais (branch da unidade, dia 8)
- Feriado Nacional (global branchId null, dia 15)
- Mostra de Arte dos Alunos (branch, dia 22)
- Prazo final do Bimestre (branch, dia 28)
🧠 6. Contexto gerado para próximas etapas
Para Card 21 (Comunicados & Avisos)
- Criar mutations
local.calendarEvent.{create,update,delete}+local.announcement.{create,publish} - Formulário de criação com campos: title, description, startDate, endDate, isAllDay, type, branchId
- Ao salvar, o evento aparece automaticamente na agenda dos alunos (sem código extra no frontend)
Para Card 8 (Home da Família)
- Pais veem agenda dos filhos: ajustar
student.calendar.monthpara aceitarstudentUserIdopcional (default = aluno autenticado; se PARENT, passar userId do filho)
Para Card 9 (Acompanhamento PARENT)
- Replicar
/aluno/agendapara/pais/agendareusando os mesmos componentes (Calendar, painel)
🎯 7. Próximo card
Card 8 [M2] Home da Família (Mobile)
- Spec:
docs/execucao/08-m2-home-familia.md - Foco: portal PARENT mobile-first, onde responsáveis veem tudo do filho
- Reuso: XPBar/PatentBadge (progresso do filho), TaskCard (tarefas do filho), e agora Calendar/agenda do filho (ajustar procedure para aceitar studentUserId)