Skip to content

Formação — UI (PR-7)

SSOT da camada de apresentação da seção Formação após a re-arquitetura (PR-1..PR-6.5). Toda mutação respeita ADR-018 — Least Privilege (especialista CRUD, coordenador SELECT, administrador NEGADO).

Mapa de componentes

Especialista (management)

ComponenteCaminhoResponsabilidade
FormacaoTabssrc/components/formacao/formacao-tabs.tsxTab raiz CursosTrilhas. Monta uma vez (hidden toggle — Lazy Mount Once).
CursosEspecialistasrc/components/formacao/cursos-especialista.tsxLista de cursos com CRUD, upload de imagem, mudança de status e acesso ao ModulosManager.
ModulosManagersrc/components/formacao/modulos-manager.tsxSheet com CRUD + reorder ↑/↓ por curso. Reorder usa two-phase (ordens negativas temporárias).
TrilhasManagersrc/components/formacao/trilhas-manager.tsxCRUD global de trilhas + attach/detach de cursos, reorder, ativo/inativo.

Coordenador (consumo)

ComponenteCaminhoResponsabilidade
CursosGridsrc/components/formacao/coordenador/CursosGrid.tsxGrid filtrável por busca e trilha. Card vertical com banner-topo, título destaque e ProgressoCursoFooter. Bloqueia clique em em_breve (aria-disabled).
TrilhasSidebarsrc/components/formacao/coordenador/TrilhasSidebar.tsxLista trilhas ativas; clique toggle. Botão Limpar só aparece com seleção.
CursoThumbnailsrc/components/formacao/coordenador/CursoThumbnail.tsxVariantes square (legado, ícone+cor) e banner (aspect 16/9, cor chapada da categoria). Usa signed URL do bucket privado quando imagem_url existe. Não chama o hook quando imagem_url é nulo.
ProgressoCursoFootersrc/components/formacao/coordenador/ProgressoCursoFooter.tsxRodapé reutilizável: barra de progresso (verde bg-curso-verde) + texto X de Y aulas assistidas | duração. Silencioso quando totalAulas === 0. Compartilhado entre CursosGrid e CursoIntroCard.
CursoDetalhesrc/components/formacao/coordenador/CursoDetalhe.tsxLayout 2-colunas (lg:grid-cols-[1fr_380px]): esquerda = busca + acordeão de módulos como cards; direita = CursoIntroCard sticky. Em viewports < lg, intro empilha acima.
CursoIntroVideosrc/components/formacao/coordenador/CursoIntroVideo.tsxEmbed YouTube/Vimeo via src/lib/video-utils.ts. URL desconhecida → null (sem throw).
CursoIntroCardsrc/components/formacao/coordenador/CursoIntroCard.tsxCard lateral do detalhe que reutiliza o átomo de card: banner topo (vídeo intro embed se válido, senão CursoThumbnail variant="banner") + título "Vídeo de introdução" + descrição + contagem de módulos + ProgressoCursoFooter.
ModuloAccordionItemsrc/components/formacao/coordenador/ModuloAccordionItem.tsxCard colapsável com profundidade (border rounded-lg bg-card shadow-sm). Header Módulo 0X | Título + contador. Aulas em linhas com Aula 0X | Título à esquerda e duração mono à direita. readOnly em em_breve.
CursoPlayersrc/components/formacao/coordenador/CursoPlayer.tsxPlayer com sidebar module-aware (módulo da aula atual abre por padrão). Fallback explícito para URL inválida.

Identidade visual (tokens semânticos)

Cores de cursos vivem em src/styles/globals.css (HSL) e são expostas em tailwind.config.ts como colors.curso.*:

  • bg-curso-azul, bg-curso-roxo, bg-curso-laranja, bg-curso-verde, bg-curso-vermelho, bg-curso-amarelo, bg-curso-cinza, bg-curso-rosa, bg-curso-indigo, bg-curso-teal, bg-curso-ambar, bg-curso-slate
  • text-curso-titulo — destaque de título de curso (violeta do mock).

src/components/videos/helpers.ts (CATEGORIA_MAP / TIPO_CURSO_MAP) mapeia categoria_visual/tipo_curso para esses tokens. Proibido voltar a bg-blue-500/bg-[#hex] — viola o Core "no hardcoded colors".

Helpers puros

src/components/formacao/coordenador/helpers.ts (todos sem side effects, testados):

  • totaisCursoCompleto(curso) — agrega contagens e duração.
  • filtrarModulosPorTermo(modulos, termo) — case-insensitive; oculta módulos vazios.
  • encontrarModuloDaAula(curso, aulaId) — suporte ao sidebar module-aware.
  • proximaAula(curso, aulaId) — pronta para auto-avanço futuro.
  • mergeAulaDetalhada(resumo, detalhe) — adapter isolando drift entre AulaResumo e VideoDetalhado.

Hooks

HookPapelEdge functionqueryKeyInvalidações
useModulos(cursoId)especialistaespecialista-modulos (list)['formacao','modulos',cursoId]mutations create/update/delete/reorder invalidam a mesma key
useTrilhas()especialistaespecialista-trilhas (list)['formacao','trilhas']todas as mutations + attach/detach_curso
useTrilhasCoord()coordenadorcoordenador-videos (list_trilhas)['formacao','trilhas-coord']read-only
useCursoCompleto(cursoId)coordenadorcoordenador-videos (list_curso_completo)['formacao','curso-completo',cursoId]read-only; compartilhada entre CursoDetalhe e CursoPlayer
useCursoImagemUpload()especialistaespecialista-cursos (upload_imagem)invalida ['cursos'] e ['formacao']
useCursoImagemSignedUrl(path)amboscursos-imagens-signed-url (sign)['cursos-imagens-signed',path], staleTime: 50 minTTL ≤1h

Estados visuais

  • Anti-Flash (docs/development/ATOMIC_RENDERING.md): todo container Formação faz if (isLoading && !data) return <Skeleton />. Nunca renderiza grid vazio antes do primeiro fetch.
  • em_breve: badge visual + aria-disabled no card; toda aula propaga readOnly. Não chama mutations.
  • Fallback de iframe (CursoIntroVideo e CursoPlayer): URL com plataforma desconhecida renderiza placeholder data-testid="video-fallback" em vez de <iframe src="">.
  • Empty states distintos: busca sem resultado vs trilha sem cursos vs sem cursos publicados — cada um com microcopy próprio.

Guardrails ADR-018 na UI

  • <FeatureGate bypassAdmin={false}> envolve todo controle de management. Admin não vê o tab Formação, nem botões de CRUD.
  • Coordenador renderiza apenas CursosGrid/TrilhasSidebar/CursoDetalhe/CursoPlayer. Qualquer chamada a especialista-* é proibida no client e bloqueada pela edge function (requireRole('especialista')).
  • Upload de imagem (useCursoImagemUpload) valida MIME (jpeg|png|webp) e tamanho (≤5 MB) fail-fast antes de tocar storage; o blob nunca fica público (leitura sempre via signed URL).

Cobertura de testes (snapshot PR-7)

bunx vitest run src/components/formacao src/hooks/formacao74 testes verdes / 13 arquivos.

  • Hooks: useModulos, useTrilhas, useTrilhasCoord, useCursoImagemUpload.
  • Especialista: ModulosManager, TrilhasManager.
  • Coordenador: CursosGrid, TrilhasSidebar, CursoThumbnail, CursoDetalhe, CursoIntroVideo, CursoPlayer, helpers.

Referências

  • SCHEMA.md — modelo de dados.
  • EDGE_FUNCTIONS.md — contratos e validação Zod.
  • ROADMAP.md — entregas PR-1..PR-7.
  • ADR-003 — payload dual-format (body.params ?? body).
  • ADR-018 — Formação Least Privilege.
  • ADR-010 — Atomic rendering.