Skip to content

Navegação e Roteamento

Última atualização: 2026-03-20


1. Estrutura de Rotas

Definidas em src/routes.tsx:

RotaComponenteTipoDescrição
/escola/:slugPortalEscolaPagePúblicaMural Olímpico do aluno/responsável
/cadastro/trialCadastroTrialPagePúblicaAuto-cadastro Trial (4 campos)
/cadastro/:tokenCadastroCompletoPagePúblicaCadastro via link do admin
/*AppAutenticadaLogin + dashboard por papel
*PaginaNaoEncontradaPública404 neutro (não vaza estrutura)

QueryClient Global

typescript
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,   // 5 minutos
      gcTime: 30 * 60 * 1000,     // 30 minutos
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

Página 404

  • Não revela papéis, rotas internas ou estrutura do sistema
  • Se usuário autenticado (cookie olp_auth): botão "Voltar ao login" (limpa cookie)
  • Se não autenticado: botão "Página inicial"

2. Registry Centralizado de Sidebars (SSOT)

Arquivo: src/lib/navigation/sections-registry.ts

Este arquivo é a fonte única de verdade para:

  1. Itens de menu das sidebars (Escola, Coordenador, Diretor)
  2. Mapeamento seção → permissão no banco (permissao_area)
  3. Lista de permissões disponíveis no CRUD da Escola

Seções por Papel

Escola (papel: escola) — 5 seções

IDLabelpermissionKey
dashboard_escolaDashboarddashboard_escola
alunosAlunosalunos
usuariosUsuáriosusuarios
pagamentosPagamentospagamentos
configuracoesConfiguraçõesconfiguracoes

Coordenador (papel: coordenador) — 8 seções

IDLabelpermissionKey
agendaAgendaagenda
calendarioCalendáriocalendario
olimpiadasOlimpíadasolimpiadas_coord
resultadosResultadosresultados
comunicacaoMensagenscomunicacao
inscricoesInscriçõesinscricoes
formacaoFormaçãoformacao
muralMural Olímpicomural

Diretor (papel: diretor) — 4 seções

IDLabelpermissionKey
painel_geralPainel Geralpainel_geral
uso_plataformaUso da Plataformauso_plataforma
projeto_olimpicoProjeto Olímpicoprojeto_olimpico
financeiroFinanceirofinanceiro

Seção Inicial por Papel

PapelSeção Inicial
administradoradmin-dashboard
especialistabanners
escoladashboard_escola
coordenadoragenda
diretorpainel_geral

3. Sistema de Permissões

Última refatoração estrutural: Abr/2026 — Escopo por papel_id

Conceito

O Administrador pode atribuir/remover seções de acesso de qualquer papel com permissões (escola, coordenador, diretor). Cada vínculo papel + escola tem um conjunto independente de permissões e sub-permissões.

Escopo por Papel (papel_id) — Abr/2026

Antes de Abr/2026, permissões eram escopadas apenas por (usuario_id, escola_id), causando colisões quando um mesmo usuário tinha múltiplos papéis na mesma escola (ex: coordenador + diretor). A refatoração adicionou papel_id como dimensão obrigatória:

ANTES: usuarios_escola_permissoes UNIQUE (usuario_id, escola_id, permissao)
AGORA: usuarios_escola_permissoes UNIQUE (usuario_id, escola_id, papel_id, permissao)

Impactos:

  • Cada badge de papel no modal admin carrega/salva permissões isoladamente
  • O endpoint /me resolve o papel_id do papel ativo antes de consultar permissões
  • Seed de permissões ao criar vínculo é escopado por papel_id
  • Sub-permissões seguem o mesmo escopo: (usuario_id, escola_id, papel_id, sub_permissao)

Modelo de Dados

TabelaEscopoDescrição
usuarios_escola_permissoes(usuario_id, escola_id, papel_id, permissao)Seções de menu (ex: agenda, painel_geral)
usuarios_escola_sub_permissoes(usuario_id, escola_id, papel_id, sub_permissao)Tabs/funcionalidades (ex: alunos.importacao)

Enum no Banco: permissao_area

Valores válidos correspondem aos permissionKey de cada papel.

Helpers do Frontend

typescript
import {
  getSectionsForRole,
  filterSectionsByPermissions,
  hasPermissionForSection,
  getPermissionsForRole,
  getInitialSectionForRole,
} from '@/lib/navigation/sections-registry';
FunçãoUso
getSectionsForRole(role)Lista todas as seções de um papel
filterSectionsByPermissions(role, keys)Filtra seções por permissões do usuário
hasPermissionForSection(sectionId, role, perms)Verifica acesso a seção específica
getPermissionsForRole(frontendRole)Para CRUD de permissões no modal de usuários
getInitialSectionForRole(role)Seção padrão ao fazer login

Backend: _shared/permissions.ts

Espelha exatamente o registry do frontend:

typescript
import { validatePermissionsForRole } from '../_shared/permissions.ts';

const { validas, invalidas } = validatePermissionsForRole('coordenador', permissoes);

Fluxo de Resolução (Backend → Frontend)

Usuário faz login → /me
  1. Resolve papel ativo (principal_role) do JWT
  2. Busca papel_id do vínculo ativo em usuario_papeis
  3. Consulta usuarios_escola_permissoes WHERE (usuario_id, escola_id, papel_id)
  4. Consulta usuarios_escola_sub_permissoes com mesmo escopo
  5. Cruza com feature_flags (ativa_global + canary)
  6. Retorna menuItems[] + permissoes[] + canaryFlags[]

Lazy Seed de Sub-Permissões

Quando um usuário tem permissões de seção ativas mas zero sub-permissões (dados legados pré-Abr/2026), o backend realiza "lazy seed": semeia automaticamente todas as sub-flags da seção como ativas.

Permissões Recomendadas (Obrigatórias)

PapelPermissão mínima
coordenadoragenda
diretorpainel_geral

O backend alerta (log) ao salvar 0 permissões, mas não bloqueia.

⚠️ Sincronização Obrigatória

Qualquer alteração em seções/permissões DEVE ser replicada em 3 locais:

  1. src/lib/navigation/sections-registry.ts (frontend)
  2. supabase/functions/_shared/permissions.ts (backend)
  3. ENUM public.permissao_area (via migration SQL)

4. Sidebar Unificada

A UnifiedSidebar (src/components/unified-sidebar.tsx) é o componente único de navegação para todos os papéis, incluindo Admin e Especialista. Recebe menuItems do backend via useMyMenu (dados pré-filtrados pela Edge Function /me).

  • Admin: Menu items definidos em ADMIN_MENU_ITEMS de _shared/permissions.ts, retornados integralmente (bypass de flags e permissões)
  • Especialista: Menu items definidos em ESPECIALISTA_MENU_ITEMS, filtrados por feature flags ativas (sujeito a canary)
  • Escola/Coordenador/Diretor: Menu filtrado por usuarios_escola_permissoes (escopado por papel_id) + feature flags. Desde Abr/2026, permissões são isoladas por papel — um usuário com coordenador e diretor na mesma escola tem conjuntos independentes de permissões e sub-flags.

Nota: Os componentes legados sidebar-admin.tsx, sidebar-especialista.tsx e sidebar.tsx foram removidos em Mar/2026. A UnifiedSidebar os substituiu integralmente.


5. Navegação do Portal

O Portal (/escola/:slug) possui navegação própria, independente das sidebars do sistema:

  • Aluno: Abas "Notícias" + "Competidor"
  • Responsável: Aba "Competidor" + seletor de filhos

Ver: docs/mural/MURAL_OVERVIEW.md


6. Sub-Views Internas (Navegação sem Sidebar)

Algumas seções do dashboard são acessadas programaticamente (ex.: clique em card) e não aparecem na sidebar. O guard de activeSection em App.tsx deve reconhecê-las para não resetar indevidamente.

Constante: SUB_VIEWS em App.tsx

Sub-viewPapelOrigem
olimpiada-detalhesEspecialistaCard de olimpíada
templates-olimpiada-detalhesEspecialistaHub de templates
template-edicaoEspecialistaEdição de template
gerenciar-videos-cursoEspecialistaVídeos de curso

Regra: ao adicionar nova sub-view interna, incluir na SUB_VIEWS do App.tsx e nesta tabela.


7. Padrão de Componente Modular (Feature Directory)

Seções complexas do dashboard são decompostas em diretórios com estrutura padronizada. A seção Agenda do coordenador é o exemplo de referência.

Estrutura: src/components/agenda/

text
src/components/agenda/
├── index.tsx                      # Orquestrador — hooks, estado, distribuição via props
├── helpers.ts                     # Tipos, interfaces, funções puras (sem React)
├── agenda-tarefas-card.tsx        # Card de tarefas pendentes
├── agenda-eventos-sidebar.tsx     # Sidebar de eventos do dia
├── agenda-evento-dialog.tsx       # Dialog de criação/edição de evento
├── agenda-nova-tarefa-dialog.tsx  # Dialog de criação de tarefa
├── agenda-detalhes-tarefa-dialog.tsx  # Sheet de detalhes da tarefa
├── agenda-historico-dialog.tsx    # Dialog de histórico de tarefas

Regras

  1. index.tsx é o único export público — importado como import Agenda from '@/components/agenda'
  2. Hooks de dados (React Query) vivem no index.tsx, nunca nos filhos
  3. Subcomponentes recebem dados e callbacks via props tipadas
  4. helpers.ts não importa React — apenas tipos e funções puras
  5. Nomes de arquivo usam prefixo do domínio (agenda-*) para evitar colisões

Outros exemplos aprovados

  • src/components/coordenador/resultados/ — 8 arquivos
  • src/components/mural-olimpico/ — 15+ arquivos
  • src/components/importacao-alunos/ — 7 arquivos

Ver padrão completo em docs/development/CODING_STANDARDS.md §16.