Navegação e Roteamento
Última atualização: 2026-03-20
1. Estrutura de Rotas
Definidas em src/routes.tsx:
| Rota | Componente | Tipo | Descrição |
|---|---|---|---|
/escola/:slug | PortalEscolaPage | Pública | Mural Olímpico do aluno/responsável |
/cadastro/trial | CadastroTrialPage | Pública | Auto-cadastro Trial (4 campos) |
/cadastro/:token | CadastroCompletoPage | Pública | Cadastro via link do admin |
/* | App | Autenticada | Login + dashboard por papel |
* | PaginaNaoEncontrada | Pública | 404 neutro (não vaza estrutura) |
QueryClient Global
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:
- Itens de menu das sidebars (Escola, Coordenador, Diretor)
- Mapeamento seção → permissão no banco (
permissao_area) - Lista de permissões disponíveis no CRUD da Escola
Seções por Papel
Escola (papel: escola) — 5 seções
| ID | Label | permissionKey |
|---|---|---|
dashboard_escola | Dashboard | dashboard_escola |
alunos | Alunos | alunos |
usuarios | Usuários | usuarios |
pagamentos | Pagamentos | pagamentos |
configuracoes | Configurações | configuracoes |
Coordenador (papel: coordenador) — 8 seções
| ID | Label | permissionKey |
|---|---|---|
agenda | Agenda | agenda |
calendario | Calendário | calendario |
olimpiadas | Olimpíadas | olimpiadas_coord |
resultados | Resultados | resultados |
comunicacao | Mensagens | comunicacao |
inscricoes | Inscrições | inscricoes |
formacao | Formação | formacao |
mural | Mural Olímpico | mural |
Diretor (papel: diretor) — 4 seções
| ID | Label | permissionKey |
|---|---|---|
painel_geral | Painel Geral | painel_geral |
uso_plataforma | Uso da Plataforma | uso_plataforma |
projeto_olimpico | Projeto Olímpico | projeto_olimpico |
financeiro | Financeiro | financeiro |
Seção Inicial por Papel
| Papel | Seção Inicial |
|---|---|
administrador | admin-dashboard |
especialista | banners |
escola | dashboard_escola |
coordenador | agenda |
diretor | painel_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
/meresolve opapel_iddo 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
| Tabela | Escopo | Descriçã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
import {
getSectionsForRole,
filterSectionsByPermissions,
hasPermissionForSection,
getPermissionsForRole,
getInitialSectionForRole,
} from '@/lib/navigation/sections-registry';| Função | Uso |
|---|---|
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:
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)
| Papel | Permissão mínima |
|---|---|
coordenador | agenda |
diretor | painel_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:
src/lib/navigation/sections-registry.ts(frontend)supabase/functions/_shared/permissions.ts(backend)- 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_ITEMSde_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 porpapel_id) + feature flags. Desde Abr/2026, permissões são isoladas por papel — um usuário comcoordenadorediretorna mesma escola tem conjuntos independentes de permissões e sub-flags.
Nota: Os componentes legados
sidebar-admin.tsx,sidebar-especialista.tsxesidebar.tsxforam removidos em Mar/2026. AUnifiedSidebaros 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-view | Papel | Origem |
|---|---|---|
olimpiada-detalhes | Especialista | Card de olimpíada |
templates-olimpiada-detalhes | Especialista | Hub de templates |
template-edicao | Especialista | Edição de template |
gerenciar-videos-curso | Especialista | Ví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/
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 tarefasRegras
index.tsxé o único export público — importado comoimport Agenda from '@/components/agenda'- Hooks de dados (React Query) vivem no
index.tsx, nunca nos filhos - Subcomponentes recebem dados e callbacks via props tipadas
helpers.tsnão importa React — apenas tipos e funções puras- Nomes de arquivo usam prefixo do domínio (
agenda-*) para evitar colisões
Outros exemplos aprovados
src/components/coordenador/resultados/— 8 arquivossrc/components/mural-olimpico/— 15+ arquivossrc/components/importacao-alunos/— 7 arquivos
Ver padrão completo em docs/development/CODING_STANDARDS.md §16.