feat(admin): implementar backoffice administrativo (issue #4) - #5
Conversation
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
…s, alertas, auditoria) Cinco telas no shell responsivo do backoffice: login local (padrão dev, mesmo fluxo dos outros apps), painel de indicadores por RiskLevel, listagem somente leitura de microáreas/ACS, consulta filtrável de alertas (sem reclassificação de risco) e log de auditoria com self-audit do próprio admin. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
…tient-app A issue citava Flutter 3.24.0 para este job, mas o ci.yml real fixa 3.44.8 nos jobs patient-app/acs-app -- usei a versão que já roda no pipeline, não a citada na issue. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
Screenshots reais ficam pendentes nesta entrega -- ver descrição do PR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
… teste _InfoRow (painel de indicadores) e os DropdownButtonFormField de filtro de alertas estouravam a largura em telas <640px -- pego pelos próprios testes de responsividade (flutter test acusou RenderFlex overflow). Corrigido com Expanded/isExpanded. Também faltava o import de flutter/material.dart em audit_log_screen_test.dart (flutter analyze acusou). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
… apps Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
…orms=web) Faltava o scaffolding de plataforma -- flutter run/build web falhava com 'This application is not configured to build on the web.'. Adiciona web/, .metadata, .gitignore próprio do app e README.md no padrão de apps/patient e apps/acs. O test/widget_test.dart gerado pelo comando (teste padrão do contador, referenciando uma classe MyApp inexistente) foi removido -- os testes reais já estão em test/*_test.dart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
O banner 'ambiente de desenvolvimento' usava AdminColors.yellow na borda e no ícone, e o chip de status do ACS na tela de microáreas usava AdminColors.green para Ativo -- exatamente o padrão que a issue proíbe explicitamente (amarelo para aviso, verde para status, não para RiskLevel). Banner agora usa AdminColors.accent (índigo, neutro); o chip de status usa ícone (check/remove) em vez de cor para diferenciar ativo/inativo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
Investiguei ligar de verdade (apps/patient e apps/acs já fazem isso, via sinalacs_client + BackendClient) antes de decidir manter local. Motivo: backend/sinalacs_server/lib/src/endpoints/auth_endpoint.dart só aceita role patient ou acs -- não existe usuário fixo de dev para admin, a chamada real falharia com AlertValidationException. Ligar isso exige mudança no backend, fora do escopo desta issue (app apps/admin). Ver descrição do PR para o achado completo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
A frase antiga dizia que paciente/ACS também não chamam o backend hoje -- ficou desatualizada depois que os dois passaram a autenticar de verdade contra auth.developmentLogin. Corrige e explica por que o admin continua local (mesmo motivo do commit anterior: endpoint só aceita role patient/acs). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
Capturadas rodando flutter run -d web-server e abrindo localhost manualmente no navegador, dados fictícios. Remove a nota de pendência em telas-admin.md, que já referenciava esses arquivos pelo nome. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical authentication and moderate data-loading and audit error-handling issues remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a Flutter Web administrative backoffice with responsive navigation, mock data, dashboards, alerts, microareas, and audit logs.
Changes:
- Adds admin screens, theme, and local development login.
- Adds data-source contracts, mock implementation, tests, and documentation.
- Adds Web scaffolding and an admin CI job.
File summaries
| File | Description |
|---|---|
docs/telas-admin.md |
Admin screen documentation. |
apps/admin/web/manifest.json |
PWA metadata. |
apps/admin/web/index.html |
Flutter Web shell. |
apps/admin/test/mock_admin_data_source_test.dart |
Mock data-source tests. |
apps/admin/test/micro_areas_screen_test.dart |
Microareas screen tests. |
apps/admin/test/login_flow_test.dart |
Login flow tests. |
apps/admin/test/audit_log_screen_test.dart |
Audit log tests. |
apps/admin/test/alerts_screen_test.dart |
Alerts screen tests. |
apps/admin/test/admin_home_shell_test.dart |
Admin shell tests. |
apps/admin/README.md |
Admin app documentation. |
apps/admin/pubspec.lock |
Locked dependencies. |
apps/admin/lib/main.dart |
Application entry point. |
apps/admin/lib/core/data/mock_admin_data_source.dart |
Mock administrative data source. |
apps/admin/lib/core/data/admin_data_source.dart |
Data-source interface and models. |
apps/admin/lib/app/app.dart |
Login, navigation, and administrative screens. |
apps/admin/lib/app/admin_theme.dart |
Admin theme. |
apps/admin/analysis_options.yaml |
Analyzer configuration. |
apps/admin/.metadata |
Flutter project metadata. |
apps/admin/.gitignore |
Project exclusions. |
.github/workflows/ci.yml |
Admin CI job. |
Review details
Suppressed comments (9)
apps/admin/lib/app/app.dart:244
- Se
fetchDashboardIndicators()falhar,snapshot.hasDatapermanece falso mesmo quando o Future termina com erro, então a tela fica em um spinner indefinidamente e oculta falhas de rede/backend. Tratesnapshot.hasErrorantes desse ramo e mostre um estado de erro, idealmente com retry.
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
apps/admin/lib/app/app.dart:325
- O mesmo FutureBuilder deixa qualquer erro de
fetchMicroAreas()preso no spinner: com erro,snapshot.hasDatanunca fica verdadeiro. Isso faz uma falha do backend parecer uma tela carregando para sempre; tratesnapshot.hasErrorexplicitamente antes de renderizar o loading.
final areas = snapshot.data ?? const [];
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
apps/admin/lib/app/app.dart:431
- Quando
fetchAlertsfalha,snapshot.datafica nulo e este ramo exibe “Nenhum alerta...” como se a consulta tivesse retornado vazia. Uma indisponibilidade pode assim ocultar alertas vermelhos; tratesnapshot.hasErrorantes do estado vazio e mostre uma falha (idealmente com retry).
else if (alerts.isEmpty)
const Padding(padding: EdgeInsets.only(top: 24), child: Text('Nenhum alerta para o filtro selecionado.'))
apps/admin/lib/app/app.dart:487
- Se
fetchAuditLogs()falhar,entriestambém vira vazio e a tela afirma “Nenhum acesso registrado ainda”, ocultando a falha de leitura dos logs. Diferenciesnapshot.hasErrordo resultado vazio e mostre um erro/retry.
final entries = snapshot.data ?? const [];
apps/admin/lib/app/app.dart:369
- Aqui o Future de
recordAccess()também é descartado. Uma falha na fonte real deixa a consulta de alertas disponível sem registrar o acesso e gera uma exceção assíncrona não tratada; aguarde o registro e trate falha antes de mostrar a lista.
widget.dataSource.recordAccess(actionType: 'view', resourceType: 'alerts');
apps/admin/lib/app/app.dart:38
- O repositório removeu a senha pré-preenchida do app ACS (
apps/acs/lib/app/app.dart:98-100), e este123456volta a embutir uma credencial no bundle. Mesmo sendo mock, isso pode ser confundido com uma senha válida ou reutilizado por engano; mantenha o campo vazio e documente o modo de desenvolvimento separadamente.
final _senha = TextEditingController(text: '123456');
apps/admin/lib/app/app.dart:393
DropdownButtontratavalue == nullcomo ausência de seleção, portanto este item nulo não mantém o rótulo “Todas” visível: o campo começa ou volta do filtro com aparência vazia. Use um valor sentinela não nulo (mapeado para filtro nulo) ou umhintpara deixar o estado sem filtro claro.
DropdownMenuItem(value: null, child: Text('Todas')),
apps/admin/lib/app/app.dart:418
- O filtro de status tem o mesmo problema do filtro de microárea: com
valuenulo, “Todos” não é exibido como seleção atual e o campo fica visualmente vazio após limpar o filtro. Use um sentinela não nulo ou umhintexplícito.
const DropdownMenuItem(value: null, child: Text('Todos')),
apps/admin/web/manifest.json:9
- O manifesto ainda declara
portrait-primarye os metadados padrão do Flutter. Como este backoffice é desktop-first e tem layout responsivo, uma instalação PWA pode ficar restrita a retrato e exibir splash/chrome azul genérico, divergindo do tema índigo/escuro do app. Ajuste esses metadados para o backoffice, usando orientaçãoanye as cores/descrição do produto.
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
- Files reviewed: 19/30 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| void _login() { | ||
| Navigator.of(context).pushReplacement( | ||
| MaterialPageRoute(builder: (_) => AdminHomeShell(dataSource: widget.dataSource)), | ||
| ); |
| @override | ||
| void initState() { | ||
| super.initState(); | ||
| widget.dataSource.recordAccess(actionType: 'view', resourceType: 'micro_areas'); |
| items: const [ | ||
| DropdownMenuItem(value: null, child: Text('Todas')), | ||
| DropdownMenuItem( | ||
| value: 'Microárea 12 — Zona Rural', | ||
| child: Text('Microárea 12 — Zona Rural', overflow: TextOverflow.ellipsis), |
| if (recordSnapshot.connectionState != ConnectionState.done) { | ||
| return const Center(child: CircularProgressIndicator()); |
|
Olá @vinimartinsufrr , obrigado pelas suas contribuições. Visando atender certos critérios de qualidade, segue aqui nesta ISSUE uma revisão que necessita de alguns ajustes no seu PR: #5 (review) Verifique as alterações necessárias e faça uma atualização nesta PR. |
Corrige os 9 pontos da review em pull/5#pullrequestreview-5199741445: - Indicadores, microáreas, alertas e auditoria: FutureBuilder só checava hasData, então um erro real ficava com spinner infinito (indicadores) ou era mostrado como se a lista estivesse vazia (alertas, auditoria) -- escondendo, por exemplo, uma falha ao carregar alertas vermelhos. Todas as quatro telas agora tratam snapshot.hasError com um estado de erro próprio e botão de retry (_AsyncError, cor neutra, não usa vermelho/amarelo/verde -- essas cores continuam reservadas a RiskLevel). - Microáreas e alertas: recordAccess() era chamado sem await no initState (fire-and-forget), então uma falha no registro de auditoria não impedia a tela de mostrar dado sensível, e virava exceção assíncrona não tratada. Agora o acesso é registrado antes dos dados serem buscados/ exibidos, no mesmo espírito do que AuditLogScreen já fazia certo. - Dropdown de filtro (microárea e status): com value null, o DropdownButton mostra o hint, não o child do item -- sem hint definido, o campo ficava em branco ao selecionar Todas/Todos em vez de mostrar o rótulo. Adicionado hint explícito nos dois. - Login: campos de matrícula/senha vinham pré-preenchidos (admin.dev/123456), diferente do padrão atual do ACS (que remove esse preenchimento). Removido; o botão continua avançando independente do que for digitado, já documentado no próprio LoginScreen. - web/manifest.json e web/index.html: description, theme/background color e orientation ainda eram o padrão gerado pelo flutter create (azul do Flutter, A new Flutter project., portrait-primary), não o tema índigo/escuro e o layout desktop-first do backoffice. Durante a correção, os próprios testes novos (próximo commit) pegaram mais um bug: setState(() => _future = fetch()) retorna a Future da atribuição (regra de que x = y vale y em Dart), o que o Flutter rejeita. Corrigido para corpo de bloco nos dois retries que tinham esse padrão (Indicadores e Microáreas). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
Cobre os quatro FutureBuilder corrigidos (indicadores, microáreas, alertas, auditoria): erro não pode aparecer como vazio nem travar em spinner, e o botão Tentar novamente precisa recuperar de verdade. Mais um teste de regressão do hint dos dropdowns (Todas/Todos visíveis fechados). Duplo de teste novo em test/support/failing_admin_data_source.dart, que permite forçar falha em qualquer método uma única vez. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
Os outros 2 comentários inline (pull/5#discussion_r4006859849 e r4006859916) já tinham sido corrigidos sem querer no commit anterior (mesmo bug do recordAccess sem await e do hasError da auditoria, só que relatado como comentário de linha em vez de no resumo da review). Restavam estes dois: - Login sem nenhum controle de acesso: o botão Entrar ignorava matrícula/senha e não tinha gate nenhum, então qualquer pessoa clicava e via alertas/auditoria, mesmo numa build fora de dev. O banner é só aviso, não bloqueia nada. Adiciona devLoginEnabled (default kDebugMode, false em build profile/release) que desabilita o botão de verdade fora de modo debug -- não resolve a falta de auth institucional (fora de escopo, já documentado), mas fecha o buraco de qualquer um entrar em qualquer build. - Filtro de microárea em Alertas: os itens do dropdown eram três strings fixas no código (as do seed mock). Trocando o AdminDataSource por uma fonte real, ou surgindo uma microárea nova, os alertas dela continuariam retornando mas sem opção de filtro correspondente. As opções agora vêm de fetchMicroAreas(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
…nâmico) login_dev_gate_test.dart: com devLoginEnabled false o botão fica desabilitado e não navega; com true funciona normal. alerts_filter_dynamic_test.dart: um AdminDataSource com uma microárea fora do antigo seed mock aparece como opção no filtro, provando que as opções vêm de fetchMicroAreas() e não de uma lista fixa no código. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX
|
Bom dia Professor @hbgit, obrigado pela revisão. Tratei os 13 pontos da review do Copilot (pull/5#pullrequestreview-5199741445, sendo 9 no resumo + 4 comentários inline, sendo 2 destes últimos duplicados dos 9): Tratamento de erro (indicadores, microáreas, alertas, auditoria): os Auditoria de acesso não esperada: Login sem nenhum controle de acesso: o botão Entrar ignorava matrícula/senha e não tinha gate nenhum, ou seja, qualquer pessoa clicava e via alertas/auditoria, em qualquer build. Adicionei um gate por Filtro de microárea fixo no código: os itens do dropdown eram três strings fixas do seed mock. Agora vêm de Dropdown "em branco" ao selecionar Todas/Todos* Senha pré-preenchida no login: removida, seguindo o padrão atual do ACS. Metadados padrão do Flutter Web ( Durante a correção, os próprios testes novos pegaram mais um bug ( Adicionei 8 testes novos cobrindo especificamente esses achados (erro/retry por tela, gate de login, filtro dinâmico), além dos 11 que já existiam. Sem force-push em nenhum momento. Apenas commits novos em cima do que já estava no PR, como pede o guia de contribuição. |
CLAUDE.md e AGENTS.md não eram atualizados desde 8c5215a. Desde então, develop recebeu a branch feat/admin-backoffice (merge #5) e uma reauditoria de acessibilidade (fix(a11y)), e nenhum dos dois ficou refletido na documentação de orientação para IA — o guia ainda descrevia apps/admin como "pubspec skeleton, no implementation yet" e o CI com quatro jobs. apps/admin (backoffice) - Documentada a arquitetura real do app: AdminHomeShell com 4 telas somente leitura (Indicadores, Microáreas, Alertas, Auditoria), a interface AdminDataSource/MockAdminDataSource (mesmo padrão de DI de PatientBackend/AcsBackend, ainda sem consumir sinalacs_client), o registro de acesso via recordAccess() antes de renderizar dado sensível (PRD §4.2.2), o motivo do login não chamar auth.developmentLogin (backend só aceita role 'patient'/'acs'), o layout desktop-first (NavigationRail/NavigationBar) e o suporte a Flutter Web, exclusivo deste app entre os três. CI - CLAUDE.md e AGENTS.md ainda diziam "quatro jobs" / "jobs separados para backend e apps"; agora refletem os cinco jobs reais (serverpod-backend, backend-docker-build, patient-app, acs-app, admin-app). Acessibilidade (WCAG 2.2 AA) - Documentados os tokens redOnSurface/accentOnSurface (ACS) e dangerOnSurface/accentOnSurface (paciente) introduzidos pela reauditoria de contraste: cores clínicas de preenchimento só atingem 4.5:1 (WCAG 1.4.3) como fill, não como texto sobre Card/surfaceRaised. Adicionada uma regra de trabalho explícita nos dois arquivos: medir contraste contra a superfície onde o texto realmente renderiza, não contra o fundo do Scaffold — o erro que produziu o falso positivo original em spec/ux_accessibility_assessment.md. Leitura obrigatória - Adicionados os três documentos novos em spec/ (lgpd_data_audit.md, ux_accessibility_assessment.md, ux_ui_test_plan.md) às listas de leitura obrigatória de ambos os arquivos. Signed-off-by: Herbert Rocha <herberthb12@gmail.com>
O que muda
Leva
apps/admin/de esqueleto (sópubspec.yaml) a um backoffice Flutter navegável, no mesmo padrão visual/código deapps/acs/eapps/patient/:RiskLevel, alertas vermelhos abertos/reconhecidos, TMRAVAdminDataSource.recordAccess)Camada de dados isolada atrás de
AdminDataSource(interface) +MockAdminDataSource(implementação mock, dados fictícios espelhando os campos reais dos.spy.yamldo backend), no espírito doAlertPublisher/AlertStoredo servidor. Tema emadmin_theme.dart, seguindo a estrutura deacs_theme.dart/patient_theme.dart(accent índigo#4F46E5). Navegação responsiva:NavigationRailem telas ≥640px,NavigationBarabaixo disso.Suporte a Flutter Web adicionado (
flutter create --platforms=web .) — alvo de plataforma decidido como Web (backoffice é desktop-first), conforme a issue sugeria confirmar antes do scaffolding.Por quê
Fecha a issue #4: o
apps/admin/era o único dos três clientes (P-APP, ACS-APP, ADMIN) ainda não implementado, apesar de já reservado na arquitetura (spec/PRD_system.md§2.1) e na matriz RBAC (§4.2.2).Como testar
Divergências em relação ao texto da issue (documentadas para não deixar o revisor descobrir sozinho)
Versão do Flutter: a issue citava 3.24.0 para o job de CI; o
ci.ymlreal já fixava 3.44.8 nos jobspatient-app/acs-appno momento em que criei a branch (confirmado lendo o arquivo). Usei 3.44.8.Login não chama
auth.developmentLogin: a issue pede explicitamente esse endpoint. Investiguei implementar de verdade, seguindo o padrão queapps/patient/apps/acsjá usam (sinalacs_client+BackendClient, que passaram a existir depois que este branch foi criado). Descobri quebackend/sinalacs_server/lib/src/endpoints/auth_endpoint.dartsó aceitarole: 'patient'ourole: 'acs'— não há usuário fixo de desenvolvimento paraadmin; a chamada real falha comAlertValidationException. Ligar isso de verdade exige uma mudança no backend (endpoint compartilhado, fora do escopo desta issue de app). Mantive login local, documentado no código (LoginScreen) e emdocs/telas-admin.md. Abro uma issue separada para estenderauth.developmentLogincom o papeladmin, se fizer sentido para o grupo.Fora do escopo (por decisão explícita da issue)
Screenshots
Capturadas rodando
flutter run -d web-servere abrindolocalhostmanualmente no navegador (dados fictícios). Documentação completa por tela emdocs/telas-admin.md.Closes #4
🤖 Generated with Claude Code
https://claude.ai/code/session_01WyMMKXSksyUUNXPieFpSfX