Aparência
Monitor de Portaria (PWA)
O que é este documento — o mapa funcional e técnico do monitor de portaria do ionCLASS: o PWA que o funcionário de portaria/separação usa no horário de saída para chamar, preparar e liberar as crianças num kanban ao vivo. É uma das quatro superfícies do produto — veja as irmãs em App do Responsável, Painel Público e Painel Administrativo.
1. O que é
O monitor é a terceira superfície do ionCLASS: um PWA React para o operador de portaria (quem separa as crianças na saída). Como o app do responsável, é um cliente REST puro do backend — sem código compartilhado, só o contrato espelhado à mão (endpoints + schemas Zod). Três responsabilidades:
- Login do operador (Sanctum bearer).
- Configuração de trabalho — quais turmas/turnos/portões o operador cobre (é a porta de entrada; sem config o board fica bloqueado).
- Monitoramento — um board kanban com drag-and-drop, agrupado por sinal de chegada, atualizado ao vivo.
2. Stack técnica
| Camada | Tecnologia |
|---|---|
| Runtime | Vite 6 + React 19 + TypeScript · package manager npm |
| UI | Tailwind CSS 4 (@tailwindcss/vite), estilizado à mão com cn() (sem shadcn portado) · sonner (toasts) · lucide-react |
| Roteamento | react-router 7 (SPA + guards) |
| Estado do board | @tanstack/react-query 5 — desvio consciente vs. as outras apps, justificado pelo board ao vivo (optimistic update + refetch + reconciliação por WS) |
| Drag-and-drop | @dnd-kit/core — sem @dnd-kit/sortable: não há reordenação intra-coluna, só mover entre colunas |
| Tempo real | laravel-echo 2 + pusher-js 8 (Reverb, protocolo pusher) |
| Tipos / HTTP | zod (fonte de verdade) · axios (espelha o cliente do app) |
| PWA | vite-plugin-pwa (manifest + service worker; app-shell offline, API cross-origin nunca cacheada) |
Comandos: npm run dev (porta 5174), npm run build (tsc --noEmit && vite build), npm run typecheck, npm run preview. Não há test runner — verificação é typecheck/build + smoke manual.
3. Login do operador
POST /api/v1/auth/operator-login → { token, user }, restrito ao operador. Uma conta válida de outro papel recebe "Apenas operadores podem acessar o monitor.". O token vive no localStorage do navegador; o interceptor de request seta Authorization: Bearer …; um 401 limpa token/usuário e volta ao login. Logout: POST /v1/operator/logout.
- Escopo por escola: o operador pertence a exatamente uma escola — sem header
X-School-Id; o backend resolve a escola do próprio token. Contraste com o app do responsável, que é N:N e enviaX-School-Id. - Ver Autenticação e Autorização.
4. Work config (porta de entrada)
A tela de configuração de trabalho seleciona turmas + turnos (opcional) + portões. O board fica bloqueado até haver ≥1 turma E ≥1 portão — o estado configured é derivado no backend e qualquer um dos dois vazio mantém o board off, redirecionando para a tela de configuração.
| Endpoint | Uso |
|---|---|
GET /v1/operator/school-classes | Turmas disponíveis para o operador |
GET /v1/operator/gates | Portões/pontos de retirada da escola |
GET /v1/operator/work-config | Config atual ({ schoolClassIds, shifts, pickupPointIds, configured }) |
PUT /v1/operator/work-config | Salva a config |
5. Kanban ao vivo
O board puxa GET /v1/operator/monitor/board (lista flat de cards) e os organiza em 4 colunas por estado de retirada:
| Coluna | Significado |
|---|---|
| Aguardando | responsável sinalizou, aluno ainda não foi preparado |
| Preparando | operador está buscando/preparando o aluno |
| Pronto | aluno no ponto de retirada, aguardando o responsável |
| Retirado | retirada confirmada |
Sem configuração de trabalho → GET .../board responde 409 code: "no_work_config" e o cliente redireciona à tela de configuração.
5.1 Agrupamento por sinal (card-família)
Os cards são agrupados client-side pelo sinal de chegada: irmãos que vieram no mesmo "Estou Chegando" viram um card-família e se movem juntos entre colunas. O contexto de chegada vem em card.arrival (nullable): { signalId, origin, verifyIdentity, signaledAt, transportMode, by:{name,photoUrl}, vehicle, studentCount }.
5.2 Drag-and-drop e transições
As regras de movimento no cliente espelham o backend: só avanço sequencial — retrocesso/salto são bloqueados no drop (toast).
| Mutation | Endpoint | Efeito |
|---|---|---|
| Avançar | POST /v1/operator/students/{id}/advance ({ toState, expectedVersion }) | Um passo à frente na sequência |
| Fechar retirada | POST /v1/operator/students/{id}/confirm-pickup ({ expectedVersion }) | De Pronto para Retirado |
| Estender liberação | POST /v1/operator/students/{id}/extend-exit | Liberação física (coluna Pronto): +30 min |
| Revogar liberação | POST /v1/operator/students/{id}/revoke-exit | Liberação física (coluna Pronto): revoga a saída |
- Otimismo + reconciliação: o drag aplica a mudança no cache incrementando a versão do estado de retirada; em erro faz rollback; ao concluir invalida o board.
- Trava otimista por versão: 409 = "estado mudou" (corrida — outro operador/hardware já mudou), 422 = salto ilegal.
5.3 Cartão e toggles
Cartão: nome, matrícula, foto, contexto de chegada (quem retira, transporte, veículo, flag verificar identidade, nº de irmãos) e badge do portão. As fotos vêm de GET /api/v1/students/{id}/photo (endpoint público, sem auth) direto num <img>.
Toggles: esconder fotos (LGPD), som em nova chegada, e refresh manual.
6. Tempo real
GET /v1/operator/monitor/credentials devolve os params do Reverb + o nome do canal privado school.{id}.monitor. O cliente assina via laravel-echo e escuta .StudentStateChanged (o ponto no prefixo vem do nome público do evento); o handshake do canal é Sanctum em /api/broadcasting/auth. A autorização do canal exige permissão de monitoramento ao vivo e poder ver a escola — a mesma regra do painel web e do operador PWA.
- O payload do evento é fino e enxuto: id do aluno, estado de retirada e a versão do estado, entre outros campos mínimos.
- A reconciliação nunca regride a versão (compara a versão do estado de retirada — estado nunca volta atrás).
- Um aluno que ficou tempo demais em Pronto gera um toast de aviso.
Ver Tempo Real, Ciclo de Retirada e o runbook Runbook de Tempo Real.
7. Ver também
- Visão de Produto · Personas e Superfícies · Glossário
- Ciclo de Retirada · Tempo Real · Autenticação e Autorização · Modelo de Domínio
- Integração (controle de acesso) · Inbound · Outbound
- Superfícies irmãs: App do Responsável · Painel Público · Painel Administrativo
⚠️ Nota de contrato: o cliente espelha à mão a API versionada do backend (endpoints + schemas Zod). Ao mudar um shape no backend, os dois lados mudam em lockstep — nada garante isso automaticamente.