diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 6c79b244..680003a2 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -57,6 +57,7 @@ class HandleInertiaRequests extends Middleware return [ ...parent::share($request), 'name' => config('app.name'), + 'dashboardNavigation' => app(ContentManagement::class)->dashboardNavigation($request->user(), $contentManagementAccess), 'procurementEnabled' => app(Procurement::class)->available(), 'fundingEnabled' => app(FundingProgrammes::class)->installed(), 'homologationReserved' => (bool) config('portal.homologation_only', false), diff --git a/app/Services/ContentManagement.php b/app/Services/ContentManagement.php index 5535545f..3556f4d0 100644 --- a/app/Services/ContentManagement.php +++ b/app/Services/ContentManagement.php @@ -51,6 +51,43 @@ final class ContentManagement return $result; } + /** @param array $access + * @return list + */ + public function dashboardNavigation(?User $actor, array $access): array + { + $workflow = app(PortalEditorialWorkflow::class); + if (! $actor || ! $workflow->available() || ! $workflow->canEnter($actor)) { + return []; + } + $items = [['label' => 'Visão geral', 'href' => '/management/editorial']]; + $documents = app(DocumentManagementAccess::class); + if ($documents->full($actor) || $documents->editorial($actor)) { + foreach (['article' => 'Artigos', 'legislation' => 'Legislação', 'document' => 'Documentos'] as $kind => $label) { + $items[] = ['label' => $label, 'href' => '/management/editorial?kind='.$kind]; + } + } + foreach (['opportunities' => 'Oportunidades', 'clusters' => 'Competitividade', 'portfolio' => 'Carteira', 'demography' => 'Demografia'] as $key => $label) { + if (isset($access[$key]['index'])) { + $items[] = ['label' => $label, 'href' => $access[$key]['index']]; + } + } + if (app(PortalEditorialSubjects::class)->available() && $actor->can('manage-portal')) { + $items[] = ['label' => 'Estruturas e relações', 'href' => '/management/editorial/subjects']; + } + $native = app(PortalEditorialNativeNotices::class); + if ($native->access($actor, 'territorial-editorial-v1')) { + $items[] = ['label' => 'Conteúdos territoriais', 'href' => '/management/territorial/editorial']; + } + if ($native->access($actor, 'investment-zones')) { + $items[] = ['label' => 'Zonas e indicadores', 'href' => '/management/investment/zones']; + } + + $items[] = ['label' => 'Notificações', 'href' => '/management/editorial#notificacoes']; + + return $items; + } + /** * @return array */ diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 661c8821..d9adeb9f 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -1,3 +1,15 @@ +## 14/11/2026 — Administração principal e apresentação agrupada do dashboard em QA + +Pedido expresso da Tempo: Administração fica no menu principal e Dashboard reúne as actividades num dropdown interno. Esta alteração posterior substitui, apenas para contas com destinos administrativos autorizados, a anterior opção de manter Administração dentro de Mais. Visitantes e contas sem esses destinos conservam os seis acessos públicos; não se apresenta um menu administrativo vazio. + +O catálogo de navegação comum é reutilizado no cabeçalho e na navegação lateral. Administração contém Dashboard → Visão geral, Artigos, Legislação, Documentos, Oportunidades, Competitividade, Carteira, Demografia, Estruturas e relações, Conteúdos territoriais, Zonas e indicadores e Notificações, apenas conforme as autorizações e componentes existentes. O painel operacional anterior também passa para Dashboard quando a conta já tem esse destino autorizado. As restantes operações administrativas mantêm os seus destinos, sem duplicar ligações. O grupo activo acompanha a página actual. Em ecrãs pequenos os subgrupos expandem por toque/teclado, sem exigir abertura por hover. + +A página `/management/editorial` passa a apresentar três grupos consistentes: **Conteúdos e oportunidades**, **Investimentos e competitividade**, **Território e dados**. Os acessos, contadores e estados existentes ficam alinhados em cartões, com adaptação de uma a três colunas, fila de conteúdos e notificações identificadas. Retiraram-se as ligações dispersas e o segundo bloco repetido de oportunidades. Guardas, filtros, decisões e histórico não mudam. + +Provas em `outputs/dashboard-navigation-20261114`: 26 testes de navegação/apresentação, integração de avisos/acesso 4/32, análise estática, tipos e compilação aprovados. Testam-se ausência de administração para visitantes/contas sem âmbito, preservação de destinos, deduplicação, localização do grupo activo, dropdown desktop, expansão móvel, grupos/counters e omissão de áreas indisponíveis. Sem migrações, dados ou permissões alteradas. A conferência visual permanece pendente pelo bloqueio do conector já documentado; os testes de renderização não são apresentados como conferência visual. + +**Estado:** implementado/verificado em QA, ainda sem instalação desta nova apresentação. A homologação mantém `a4541298`. Preparar uma única entrega pelo processo Git existente, com backup/restauro e autorização concreta; não reinstalar o complemento anterior nem republicar conteúdos. + ## 14/11/2026 — complemento do dashboard instalado e activo em a4541298 **Instalação conjunta concluída**, após autorização expressa da Tempo. A revisão `a4541298c0a386ab8bfd8e3e531e12d432ec671b` está confirmada em `https://teste-investidor.tempo.co.mz/build/revision.json`. Utilização: `/management/editorial`, com login/MFA e os âmbitos existentes. Estruturas/relações/cadeias, revisão das zonas e indicadores documentais e notificações nativas estão activos. A designação jurídica nominal recebida foi aplicada à configuração protegida; a conta existe e tem correio verificado. Não se alteraram permissões gerais nem se publicaram conteúdos automaticamente. diff --git a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv index a93a7cb0..81ddd58c 100644 --- a/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv +++ b/docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv @@ -11128,7 +11128,7 @@ AUD-SIM-L1-P133,Saída de regimes suspensivos e operações de viagens,Refinamen INE-DEMO-01,"Integração nacional de dados demográficos INE na viabilidade existente, importador em duas etapas, séries/versionamento, comparação territorial e adenda nacional.",Pedido colado pelo proprietário nesta sessão; execução expressamente confirmada; Macate/Vanduzi autorizados apenas em QA em 13/11/2026.,Território; viabilidade; percurso; SWOT; simulador,/localizacoes/demografia; /api/v1/investment/demography; /management/demography,DemographicImport; DemographicEvidence; leitor XLSX; cadastro/mapa/comparação/simulador e fontes SWOT existentes reutilizados.,"Homologação:4988ca89 instalado,erro500 de processos corrigido;preparaçõesCLI12/920456 privadas,por confirmaçãoMFA. Local:12lotes/920456 instalados e verificados.","154706 observações reais INE; três originais com scan real/hash; repetição sem duplicação; 72 reconciliações entre exemplares; comparação GONDOLA/GURO. Homologação: backup/restauro190 tabelas/369 ficheiros;11 tabelas de negócio preservadas;168 correspondências+15 distritos autorizados,sem pendências;HTTPS200 e revisão b20068d0 confirmada. Correcção df543a44:3testes/17asserções;HTTPS confirma revisão;ambos os titulares designados passam a autorização de leitura;nenhuma permissão alterada;hashes de código/configuração conferidos. Correcção500:2testes/20asserções sem proc_open;14testes/52asserções consumidores;8preparador;PHPStan aprovado. PHP-FPM real confirmou processos bloqueados,cacheCLI e antimalware disponíveis,hashes correctos,sem actor/sessão ou escrita de negócio;HTTPS confirma4988ca89.",Código instalado e verificado no PHP-FPM real;incorporação remota dos dados pendente da confirmação autenticada.,"Confirmar coortes initial/remaining/zambezia na sessão com MFA e conferir recibos,12 lotes/920456 observações e consulta territorial. Conferência visual administrativa pendente.",PostgreSQL/PostGIS QA; ClamAV; Python stdlib; autenticação e MFA existentes; nenhum novo aprovador.,"Três ficheiros associados aos territórios, valores exactos, séries 2017–2050, consulta sectorial/temporal, duas localizações, fontes/versões, inconsistências e acessos demonstrados em QA. Homologação manual restante registada no relatório.",Alta,,,,,"Consulta pública desktop/mobile conferida; visual administrativa pendente, testes HTTP aprovados.",11 testes de integração aprovados.,Três lotes QA; 154706 observações; idempotência e unicidade verificadas.,Consulta por território/ano/exemplar; desactivação/reaprovação preservam histórico.,"População/idade/residência, vitais, fontes e limitações; sem extrapolação, procura automática ou probabilidade de sucesso.",,,,,"Sem pendência de importação dos ficheiros provinciais fornecidos. Conferência visual administrativa,ressalvas/unidades ausentes e indicadores sem valores mantidos;outros ambientes não instalados por esta operação.","PortalProfileInstallation/MFA existentes; DemographicInstallation; DemographicImport; InvestmentGeography; simulador, mapa/comparação e fontes SWOT existentes.",,Proprietário executou a disponibilização autorizada de Zambezia.xlsx e cinco distritos;recibo autenticado e resultado confirmados sem repetição.,,,"outputs/demography-20261113/zambezia/installation-status.json e verification.json:recibo autenticado09:33:38UTC,hash/lote/count126684,total12lotes/920456,preservação11lotes e oito tabelas de negócio,série provincial e cinco distritos públicos. operational-status.json actualizado;ensaios e backup/restauro anteriores preservados.",demografia-ine,13/11/2026 — instalação autenticada concluída; conferência técnica de leitura sem repetição da operação,,,,,,,,,,,,,, REL-DOSSIER-20261113,"Agrupar no dossier correspondente os relatórios produzidos pelas etapas, com versão, oportunidade e recuperação.",Pedido expresso do proprietário em 13/11/2026: «tem de ser resolvido».,Percurso do investidor; dossier; relatórios,/investimentos/{id}#relatorios; /investimentos/{id}/relatorios/fiscais/{reportId},"Índice e relatório consolidado no percurso existente; reutiliza os geradores Word/HTML, cálculos e cópias preservadas.",Homologação teste-investidor.tempo.co.mz instalada em 8854b292; /investimentos/{id}#relatorios.,"21 testes frontend; regressão servidor 4/168 asserções e caso final 45 asserções; tipos,lint,PHPStan e build aprovados. HTTPS e preservação conferidos na revisão instalada.",Instalado e conferido em 8854b292; resumos S01–S19 consultáveis e descarregáveis,QA: 19 consultas e 38 descargas Word/HTML; recuperação demonstrada. Instalação e consulta/descarga S01 conferidas na homologação. Saídas específicas ainda não produzidas mantêm o saldo próprio.,,"Mesmo dossier/oportunidade → índice por etapa → relatório Word/HTML → acesso a versões APIEX, fiscais e documentos → recuperação sem misturar oportunidades ou expor finanças a gestores.",Alta,,,,,Índice responsivo e exportação consolidada,"Descarga privada da cópia fiscal preservada, autorizada por titular e dossier","Reutiliza os registos existentes; associação fiscal nova conserva âmbito, sem reescrever fontes anteriores.",Aprovada em prova sintética; sem depender de acesso à simulação anónima original.,Versão/data/estado/origem; pendências e âmbito por confirmar explícitos.,,,,,,,,"Acrescentar índice e consolidar saídas existentes; sem módulo paralelo, publicação ou envio automático.",Resumos por etapa instalados e conferidos; originais específicos em falta mantêm o saldo próprio,,outputs/dossier-reports-20261113/verification.json; outputs/dossier-reports-20261113/homologation-installed.json; outputs/dossier-reports-20261113/stage-authenticated-download-verification.json; outputs/dossier-reports-20261113/stage-homologation-installed.json,,,,,,,,,,,,,,,, ACT-20261113-PESQUISA,Pesquisa rápida: contar corredores sem misturar referências; produtos físicos e financeiros separados por categoria e localização; como licenciar empresa.,Pedidos Tempo na conversa de 13/11/2026,Pesquisa rápida,/pesquisa; /,"SearchTerms::requestIntent, GlobalSearchController, FederatedSearch, portal/search, portal-hero; reutiliza catálogos publicados existentes.",Implementado em QA; nova candidata ainda não instalada.,"outputs/quick-search-20261113/: integration.log, regression.log, unit.log, frontend.log, types.log, analysis.log, build.log",QA implementado e testes focados aprovados; instalação e visual pendentes,Instalar após autorização concreta; conferir apresentação no navegador.,,Contagem tipada e zero distinto de falha; produtos separados com localização/cobertura e contexto nos links; licenciamento no roteiro/dossier existentes; só publicados; erros de fontes explícitos; paginação preservada.,Alta,,,,,,,,,,,,,,"Conferência visual e instalação desta candidata; pesquisa territorial é textual, sem inferir elegibilidade.",,,,Implementação QA concluída neste recorte; entrega preparada.,,,,,,,,,,,,,,,,,, -ACT-20261113-EDITORIAL-F1,"Dashboard Colaborativo Fase 1: versões e autorizações; artigos, legislação, oportunidades e documentos; filas e notificações",Directiva Tempo 13/11/2026 nesta sessão; plano aprovado e autorização de implementação apenas em desenvolvimento/QA,Gestão editorial / conteúdos,/management/editorial; /management/editorial/resources/{id}; edições/anexos/notificações; editores e circuito nativo de oportunidades,"Edições privadas imutáveis no circuito; versão pública preservada; aprovação/publicação separadas; decisões ligadas a conteúdo/anexos; permissões e auditoria reutilizadas; notificações com acesso revogável [Complemento agrupado QA 14/11/2026] Designação jurídica explícita por configuração protegida, sem conceder gestão geral; nomeação recebida da Tempo. Adaptadores nativos na mesma caixa de notificações e contadores, com âmbito actual e deduplicação.","Instalado e activo em teste-investidor.tempo.co.mz, a4541298; complemento agrupado e designação jurídica nominal disponíveis, dados anteriores preservados","outputs/editorial-phase1-20261113: integração 11/223; oportunidades 6/116 + caso reforçado 1/20; unidade 1/6; frontend 12; PHPStan/tipos/build aprovados; instalação 14/11 d5c960b6: backup/restauro 193 tabelas, quatro alterações aditivas, 416 uploads preservados, HTTP público/privado conferido; CI 132 unitários e 1902 frontend; complemento outputs/editorial-completion-20261114: testes focados finais 28/361; regressão nativa 35/540; frontend 13; tipos/build aprovados; análise estática aprovada após correcções localizadas; duas alterações aditivas ensaiadas no QA55441, preservação e repetição recusada; instalação autorizada a4541298 em 14/11: restauro 195 tabelas, 192 tabelas inalteradas, cache/sessões reconciliadas, 416 uploads preservados, 270 instituições/12 lotes/920456 observações; CI 132/611 + frontend1906; HTTP e serviços conferidos",Instalado e activo na homologação a4541298; conferência visual autenticada pendente,Conferência visual autenticada dos percursos editoriais e apresentação numa janela estreita. Instalação e designação jurídica recebida/aplicada não permanecem pendentes.,Instalação/schema concluídos; falta conferência visual; designação jurídica explícita para validação de novas versões legais,Editar preserva toda a publicação; JSON/prévias/anexos autorizados; conteúdo/anexos novos exigem nova decisão; aprovação distinta da publicação; filas e notificações isoladas; conflitos 409; histórico recuperável,Alta,,,,,"Fila, filtros, edição, histórico e notificações em QA","JSON nas mesmas rotas privadas, com as mesmas autorizações","Edições, decisões, notificações e auditoria; dados sintéticos revertidos pelos testes",Versões e publicação anterior recuperadas; isolamento/revogação demonstrados,Versão pública mantida durante edição e revisão; substituição só por publicação explícita,,,QA implementado; sem instalação operacional antes da entrega autorizada de 14/11/2026,"Revisão a4541298 confirmada no endereço servido, componentes activos e conta jurídica nominal configurada sem permissões gerais adicionais. Recibos em outputs/editorial-completion-20261114; conferência visual ainda não executada.",Conferência visual autenticada dos percursos editoriais e apresentação numa janela estreita. Instalação e designação jurídica recebida/aplicada não permanecem pendentes.,"DocumentManagementAccess, PortalProfileAccess, EditorialReading, VerifiedDocumentBinary, auditoria e circuito nativo de oportunidades",Conector visual indisponível: The browser access host is unavailable. Conferência manual pode ser efectuada pela equipa com os âmbitos existentes; não requer reinstalação.,Implementar em desenvolvimento/QA; não intervir em produção,Conferência visual do delta instalado,,"Provas QA anteriores e outputs/editorial-agrupado-20261114/destination-installation-verified.json; recibos de backup, restauro, schema, activação e CI na mesma pasta",Dashboard Colaborativo Fase 1,"14/11/2026 — instalação agrupada autorizada, verificada e preservada",,,,,,,,"Reutilização dos testes nativos de oportunidade, permissão, prévias e integridade",Versão pública preservada; circuito editorial e decisões por versão/anexos; escopos de notificações e conteúdos,"Sem conferência visual autenticada; sem novos papéis, aprovação ou publicação de conteúdos; resíduos da Fase 2 mantidos; validação jurídica requer designação nominal",,Percursos editoriais e autorizações,Artigos → legislação/documentos → oportunidades reutilizadas → fila/painel/notificações,Instalado e activo a4541298; visual autenticado pendente; restante saldo do portal preservado +ACT-20261113-EDITORIAL-F1,"Dashboard Colaborativo Fase 1: versões e autorizações; artigos, legislação, oportunidades e documentos; filas e notificações",Directiva Tempo 13/11/2026 nesta sessão; plano aprovado e autorização de implementação apenas em desenvolvimento/QA,Gestão editorial / conteúdos,/management/editorial; /management/editorial/resources/{id}; edições/anexos/notificações; editores e circuito nativo de oportunidades,"Edições privadas imutáveis no circuito; versão pública preservada; aprovação/publicação separadas; decisões ligadas a conteúdo/anexos; permissões e auditoria reutilizadas; notificações com acesso revogável [Complemento agrupado QA 14/11/2026] Designação jurídica explícita por configuração protegida, sem conceder gestão geral; nomeação recebida da Tempo. Adaptadores nativos na mesma caixa de notificações e contadores, com âmbito actual e deduplicação. [Apresentação QA 14/11] Administração principal → Dashboard com actividades autorizadas; painel agrupado em cartões por conteúdo/investimento/território, sem repetir oportunidades; navegação comum e expansão móvel preservam destinos e âmbito.","Instalado e activo em teste-investidor.tempo.co.mz, a4541298; complemento agrupado e designação jurídica nominal disponíveis, dados anteriores preservados","outputs/editorial-phase1-20261113: integração 11/223; oportunidades 6/116 + caso reforçado 1/20; unidade 1/6; frontend 12; PHPStan/tipos/build aprovados; instalação 14/11 d5c960b6: backup/restauro 193 tabelas, quatro alterações aditivas, 416 uploads preservados, HTTP público/privado conferido; CI 132 unitários e 1902 frontend; complemento outputs/editorial-completion-20261114: testes focados finais 28/361; regressão nativa 35/540; frontend 13; tipos/build aprovados; análise estática aprovada após correcções localizadas; duas alterações aditivas ensaiadas no QA55441, preservação e repetição recusada; instalação autorizada a4541298 em 14/11: restauro 195 tabelas, 192 tabelas inalteradas, cache/sessões reconciliadas, 416 uploads preservados, 270 instituições/12 lotes/920456 observações; CI 132/611 + frontend1906; HTTP e serviços conferidos; outputs/dashboard-navigation-20261114: frontend26, acesso4/32, análise estática/tipos/build aprovados; nenhuma escrita activa ou migração","Circuito instalado a4541298; reorganização de navegação/apresentação implementada em QA, ainda sem instalação; visual pendente",Autorizar/instalar a nova apresentação agrupada pelo executor Git e conferir visualmente os percursos e a janela estreita; circuito anterior instalado preservado.,Instalação/schema concluídos; falta conferência visual; designação jurídica explícita para validação de novas versões legais,Editar preserva toda a publicação; JSON/prévias/anexos autorizados; conteúdo/anexos novos exigem nova decisão; aprovação distinta da publicação; filas e notificações isoladas; conflitos 409; histórico recuperável,Alta,,,,,"Fila, filtros, edição, histórico e notificações em QA","JSON nas mesmas rotas privadas, com as mesmas autorizações","Edições, decisões, notificações e auditoria; dados sintéticos revertidos pelos testes",Versões e publicação anterior recuperadas; isolamento/revogação demonstrados,Versão pública mantida durante edição e revisão; substituição só por publicação explícita,,,QA implementado; sem instalação operacional antes da entrega autorizada de 14/11/2026,"Revisão a4541298 confirmada no endereço servido, componentes activos e conta jurídica nominal configurada sem permissões gerais adicionais. Recibos em outputs/editorial-completion-20261114; conferência visual ainda não executada.",Autorizar/instalar a nova apresentação agrupada pelo executor Git e conferir visualmente os percursos e a janela estreita; circuito anterior instalado preservado.,"DocumentManagementAccess, PortalProfileAccess, EditorialReading, VerifiedDocumentBinary, auditoria e circuito nativo de oportunidades",Autorização concreta da nova apresentação; conferência visual por conector indisponível ou equipa. O circuito a4541298 e a designação jurídica já estão instalados.,Implementar em desenvolvimento/QA; não intervir em produção,Entrega da nova apresentação e conferência visual,,"Provas QA anteriores e outputs/editorial-agrupado-20261114/destination-installation-verified.json; recibos de backup, restauro, schema, activação e CI na mesma pasta",Dashboard Colaborativo Fase 1,"14/11/2026 — instalação agrupada autorizada, verificada e preservada",,,,,,,,"Reutilização dos testes nativos de oportunidade, permissão, prévias e integridade",Versão pública preservada; circuito editorial e decisões por versão/anexos; escopos de notificações e conteúdos,"Sem conferência visual autenticada; sem novos papéis, aprovação ou publicação de conteúdos; resíduos da Fase 2 mantidos; validação jurídica requer designação nominal",,Percursos editoriais e autorizações,Artigos → legislação/documentos → oportunidades reutilizadas → fila/painel/notificações,"a4541298 activo; Administração → Dashboard e cartões profissionais em QA, sem instalação" ACT-20261113-EDITORIAL-CLUSTERS,"Separar aprovação/publicação da análise de Competitividade; preservar publicação, contexto, versões e autoridade; agrupar entrega relacionada","Directiva de continuidade e decisão de pacote conjunto do proprietário, 13/11/2026; Fase 2 já delimitada",Dashboard Colaborativo / Competitividade dos clusters,/management/clusters; /management/clusters/{id}/competitiveness/{action}; Gestão de conteúdos; Circuito editorial,"Extensão do circuito cifrado existente: submissão, revisão, aprovação e publicação distintas; devolução/rejeição/arquivo; versões/contexto; MFA e autoridade efectiva; navegação reutilizada [Complemento agrupado QA 14/11/2026] Notificações próprias de Competitividade e contadores; estruturas sem ficha, relações, etapas, ligações e lacunas reutilizam as edições e decisões comuns. Nenhum dossier paralelo; publicação anterior conservada.","Instalado e activo em teste-investidor.tempo.co.mz, a4541298; complemento agrupado e designação jurídica nominal disponíveis, dados anteriores preservados","outputs/editorial-clusters-20261113: integração 14/252; regressão Fase 1 11/223; frontend 18; PHPStan, tipos e build aprovados; instalação 14/11 d5c960b6: backup/restauro 193 tabelas, quatro alterações aditivas, 416 uploads preservados, HTTP público/privado conferido; CI 132 unitários e 1902 frontend; complemento outputs/editorial-completion-20261114: testes focados finais 28/361; regressão nativa 35/540; frontend 13; tipos/build aprovados; análise estática aprovada após correcções localizadas; duas alterações aditivas ensaiadas no QA55441, preservação e repetição recusada; instalação autorizada a4541298 em 14/11: restauro 195 tabelas, 192 tabelas inalteradas, cache/sessões reconciliadas, 416 uploads preservados, 270 instituições/12 lotes/920456 observações; CI 132/611 + frontend1906; HTTP e serviços conferidos",Instalado e activo na homologação a4541298; conferência visual autenticada pendente,Conferência visual autenticada dos percursos editoriais e apresentação numa janela estreita. Instalação e designação jurídica recebida/aplicada não permanecem pendentes.,Instalação/schema concluídos; falta conferência visual,Aprovar não publica; rascunho/recuperação preservam publicação; alteração do contexto invalida aprovação; independência quando exigida; revogação e conflitos bloqueados; retirar publicação preserva revisão privada,Alta,,,,,Editor e lista existentes com estados distintos; entradas de gestão integradas,"JSON autenticado, MFA e autorizações verificadas no servidor",Histórico cifrado existente; sem novas tabelas ou migrações deste recorte,Histórico recuperado como nova edição; publicação anterior preservada,Publicação só por confirmação distinta após aprovação da edição exacta,,,QA implementado; sem instalação operacional antes da entrega autorizada de 14/11/2026,"Revisão a4541298 confirmada no endereço servido, componentes activos e conta jurídica nominal configurada sem permissões gerais adicionais. Recibos em outputs/editorial-completion-20261114; conferência visual ainda não executada.",Conferência visual autenticada dos percursos editoriais e apresentação numa janela estreita. Instalação e designação jurídica recebida/aplicada não permanecem pendentes.,"ClusterCompetitiveness, audit_events cifrados, gestão de estruturas, PortalProfileAccess, ContentManagement e circuito Fase 1",Conector visual indisponível: The browser access host is unavailable. Conferência manual pode ser efectuada pela equipa com os âmbitos existentes; não requer reinstalação.,Agrupar funcionalidades relacionadas num pacote e instalar em conjunto quando prontas; sem intervenção operacional nesta passagem,Conferência visual do delta instalado,,"Provas QA anteriores e outputs/editorial-agrupado-20261114/destination-installation-verified.json; recibos de backup, restauro, schema, activação e CI na mesma pasta",Dashboard Colaborativo — pacote conjunto,"14/11/2026 — instalação agrupada autorizada, verificada e preservada",,,,,,,,Regressão anterior de Competitividade e Fase 1; oportunidades mantêm provas aceites,"Ciclo editorial de Competitividade, preservação pública, permissões, independência, recuperação e contexto","Sem conferência visual autenticada; sem novos papéis, aprovação ou publicação de conteúdos; resíduos da Fase 2 mantidos; validação jurídica requer designação nominal",,Circuito editorial e agrupamento de entregas,Submeter → rever → aprovar → publicar; preservar a publicação e autoridade efectiva,Instalado e activo a4541298; visual autenticado pendente; restante saldo do portal preservado ACT-20261113-EDITORIAL-CARTEIRA,Preservar fichas e dados-base/localização associados de projecto âncora/cluster/corredor durante revisão privada; publicação conjunta e pacote agrupado,"Directiva de continuidade e decisão de agrupar funcionalidades relacionadas, 13/11/2026",Dashboard Colaborativo / carteira de investimento,/management/portfolio/{id}; /management/editorial/portfolio/{id}; transições/notificações existentes,"Edições privadas na mesma tabela da Fase 1; aprovação/publicação separadas; fonte/contexto por hash; independência de todos os colaboradores; MFA e auditoria; pendências e notificações; dados-base e localização propostos na mesma edição; publicação atómica; conflitos sem sobrescrever [Complemento agrupado QA 14/11/2026] Estruturas sem ficha e relações projecto/cluster/corredor/zona, cadeias e lacunas com proposta privada, revisão, publicação e retirada expressas; versões, fontes, localização e conflitos preservados.","Instalado e activo em teste-investidor.tempo.co.mz, a4541298; complemento agrupado e designação jurídica nominal disponíveis, dados anteriores preservados","outputs/editorial-portfolio-20261113: novos 5/199; Fase 1 11/223; carteira anterior 8/201; notificações oportunidades 1/20; frontend 20; PHPStan/tipos/build aprovados; dados-base novos 6/178, estruturas/território 19/289, frontend actualizado 21; outputs/editorial-structures-20261113; instalação 14/11 d5c960b6: backup/restauro 193 tabelas, quatro alterações aditivas, 416 uploads preservados, HTTP público/privado conferido; CI 132 unitários e 1902 frontend; complemento outputs/editorial-completion-20261114: testes focados finais 28/361; regressão nativa 35/540; frontend 13; tipos/build aprovados; análise estática aprovada após correcções localizadas; duas alterações aditivas ensaiadas no QA55441, preservação e repetição recusada; instalação autorizada a4541298 em 14/11: restauro 195 tabelas, 192 tabelas inalteradas, cache/sessões reconciliadas, 416 uploads preservados, 270 instituições/12 lotes/920456 observações; CI 132/611 + frontend1906; HTTP e serviços conferidos",Instalado e activo na homologação a4541298; conferência visual autenticada pendente,Conferência visual autenticada dos percursos editoriais e apresentação numa janela estreita. Instalação e designação jurídica recebida/aplicada não permanecem pendentes.,Instalação/schema concluídos; falta conferência visual,"Guardar/aprovar mantém todos os campos públicos; publicar aplica só edição aprovada; independência, revogação, MFA e versões verificadas no servidor; contexto alterado exige nova decisão; histórico e notificações recuperáveis; dados-base/localização e ficha aplicados atomicamente; precisão do orçamento; conflitos de código e território sem publicação parcial",Alta,,,,,"Editor existente, ecrã de revisão reutilizado, versões/contexto, acesso pelo painel e Gestão de conteúdos",JSON/HTML protegidos pelas mesmas autorizações; leitura privada sem cache,Edições e decisões na tabela comum; migração QA aditiva com exclusividade do sujeito e rollback protegido,Rascunho e versões anteriores recuperados sem substituir publicação; edições deliberadas preservadas,Ficha pública mantida; nova publicação só por operação independente explícita,,,QA implementado; sem instalação operacional antes da entrega autorizada de 14/11/2026,"Revisão a4541298 confirmada no endereço servido, componentes activos e conta jurídica nominal configurada sem permissões gerais adicionais. Recibos em outputs/editorial-completion-20261114; conferência visual ainda não executada.",Conferência visual autenticada dos percursos editoriais e apresentação numa janela estreita. Instalação e designação jurídica recebida/aplicada não permanecem pendentes.,"PortalEditorialWorkflow, portal_editorial_editions, investment_dossiers, auditoria, perfis/MFA, editor e prévias nativos",Conector visual indisponível: The browser access host is unavailable. Conferência manual pode ser efectuada pela equipa com os âmbitos existentes; não requer reinstalação.,Implementar em QA e agrupar com Fase 1/Competitividade; sem intervenção operacional,Conferência visual do delta instalado,,"Provas QA anteriores e outputs/editorial-agrupado-20261114/destination-installation-verified.json; recibos de backup, restauro, schema, activação e CI na mesma pasta",Dashboard Colaborativo — pacote conjunto,"14/11/2026 — instalação agrupada autorizada, verificada e preservada",,,,,,,,"Regressão Fase 1, carteira anterior e notificações nativas; Competitividade mantém provas anteriores aplicáveis","Preservação pública, versões/contexto, independência, MFA, autoridade, histórico/notificações e bloqueio de bypass","Sem conferência visual autenticada; sem novos papéis, aprovação ou publicação de conteúdos; resíduos da Fase 2 mantidos; validação jurídica requer designação nominal",,Carteira e continuidade do circuito editorial,Ficha existente → edição privada → submissão → revisão → aprovação → publicação; consulta pública e recuperação preservadas,Instalado e activo a4541298; visual autenticado pendente; restante saldo do portal preservado ACT-20261114-EDITORIAL-DEMO,Novos lotes demográficos no circuito editorial: aprovação separada da publicação; preservar lotes e valores anteriores; entrega agrupada,"Fase 2 delimitada e directiva de continuidade; decisão de pacote agrupado, 13–14/11/2026",Dashboard Colaborativo / indicadores demográficos,"/management/demography; /management/editorial/demography/{id}; versões, transições e original protegido","Lote nativo privado mantém imported até publicação explícita; edições e decisões comuns; originais, proveniência, dados e territórios por hash; MFA e autoridade existentes; notificações e pendências [Complemento agrupado QA 14/11/2026] Recorte demográfico anteriormente instalado preservado, sem reimportar, recalcular ou reaprovar os doze lotes. Outros indicadores documentais existentes tratados na revisão nativa de zonas, não num novo conjunto demográfico.","Instalado e activo em teste-investidor.tempo.co.mz, a4541298; complemento agrupado e designação jurídica nominal disponíveis, dados anteriores preservados","outputs/editorial-demography-20261114: seis casos iniciais, incluindo 131400 observações/256MB; cinco funcionais reforçados 5/66; regressão 14/52; frontend 24; PHPStan/tipos/build aprovados; instalação 14/11 d5c960b6: backup/restauro 193 tabelas, quatro alterações aditivas, 416 uploads preservados, HTTP público/privado conferido; CI 132 unitários e 1902 frontend; complemento outputs/editorial-completion-20261114: testes focados finais 28/361; regressão nativa 35/540; frontend 13; tipos/build aprovados; análise estática aprovada após correcções localizadas; duas alterações aditivas ensaiadas no QA55441, preservação e repetição recusada; instalação autorizada a4541298 em 14/11: restauro 195 tabelas, 192 tabelas inalteradas, cache/sessões reconciliadas, 416 uploads preservados, 270 instituições/12 lotes/920456 observações; CI 132/611 + frontend1906; HTTP e serviços conferidos",Instalado e activo na homologação a4541298; conferência visual autenticada pendente,Conferência visual autenticada dos percursos editoriais e apresentação numa janela estreita. Instalação e designação jurídica recebida/aplicada não permanecem pendentes.,Instalação/schema concluídos; falta conferência visual,Aprovar não publica; original/observações/proveniência alterados impedem publicação e reaprovação silenciosa; contexto territorial exige revisão; publicar conserva séries anteriores; arquivo conserva valores e histórico; MFA/autoridade/independência quando exigida; memória limitada,Alta,,,,,"Gestão nativa e revisão comum, originais, advertências, edições, pendências e notificações",JSON/HTML protegidos; original privado; anti-bypass das operações antigas,Edições na tabela comum e eventos demográficos nativos; observações preservadas,Histórico e fontes recuperáveis sem repetir importação; dados anteriores preservados,A série só aparece nas análises existentes depois da publicação explícita,,,QA implementado; sem instalação operacional antes da entrega autorizada de 14/11/2026,"Revisão a4541298 confirmada no endereço servido, componentes activos e conta jurídica nominal configurada sem permissões gerais adicionais. Recibos em outputs/editorial-completion-20261114; conferência visual ainda não executada.",Conferência visual autenticada dos percursos editoriais e apresentação numa janela estreita. Instalação e designação jurídica recebida/aplicada não permanecem pendentes.,"DemographicImport, DemographicEvidence, PortalEditorialWorkflow, importações/observações/eventos existentes, perfis e MFA",Conector visual indisponível: The browser access host is unavailable. Conferência manual pode ser efectuada pela equipa com os âmbitos existentes; não requer reinstalação.,"Implementar em QA, conservar fontes aceites e agrupar a instalação; sem intervenção operacional",Conferência visual do delta instalado,,"Provas QA anteriores e outputs/editorial-agrupado-20261114/destination-installation-verified.json; recibos de backup, restauro, schema, activação e CI na mesma pasta",Dashboard Colaborativo — pacote conjunto,"14/11/2026 — instalação agrupada autorizada, verificada e preservada",,,,,,,,"Regressão demográfica 14/52; Fase 1, carteira e dados-base reaproveitados/repetidos apenas no código afectado","Separação aprovação/publicação; fontes, números, advertências, jurisdição, acesso, memória e preservação histórica","Sem conferência visual autenticada; sem novos papéis, aprovação ou publicação de conteúdos; resíduos da Fase 2 mantidos; validação jurídica requer designação nominal",,Publicação editorial de indicadores demográficos,"Lote privado → revisão → aprovação → publicação → análise existente → arquivo, sem alterar valores ou lotes anteriores",Instalado e activo a4541298; visual autenticado pendente; restante saldo do portal preservado diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index e7618f69..d1c56505 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -32,6 +32,7 @@ export function AppSidebar() { contentManagementAccess, profileAccess, scopedAdministration, + dashboardNavigation, } = usePage<{ auth: Auth; existingBusinessEnabled?: boolean; @@ -41,6 +42,7 @@ export function AppSidebar() { opportunityManagement?: OpportunityManagementAccess | null; contentManagementHref?: string | null; scopedAdministration?: { label: string; href: string }[]; + dashboardNavigation?: { label: string; href: string }[]; contentManagementAccess?: { opportunities?: { index: string; create?: string | null }; }; @@ -56,6 +58,7 @@ export function AppSidebar() { contentManagementHref, contentManagementAccess, scopedAdministration, + dashboardNavigation, legacyContent: currentPage.props.legacyContentEnabled as | boolean | undefined, diff --git a/resources/js/components/portal-grouped-menu.tsx b/resources/js/components/portal-grouped-menu.tsx index 098a7d4d..699e90c4 100644 --- a/resources/js/components/portal-grouped-menu.tsx +++ b/resources/js/components/portal-grouped-menu.tsx @@ -53,6 +53,7 @@ export function PortalGroupedMenu() { profileAccess?: ProfileAccess | null; opportunityManagement?: OpportunityManagementAccess | null; scopedAdministration?: { label: string; href: string }[]; + dashboardNavigation?: { label: string; href: string }[]; contentManagementHref?: string | null; contentManagementAccess?: { opportunities?: { index: string; create?: string | null }; @@ -79,6 +80,7 @@ export function PortalGroupedMenu() { contentManagementHref: props.contentManagementHref, contentManagementAccess: props.contentManagementAccess, scopedAdministration: props.scopedAdministration, + dashboardNavigation: props.dashboardNavigation, }); const current = currentNavigationGroup(groups, url); const currentItem = currentNavigationItem(groups, url); @@ -93,12 +95,17 @@ export function PortalGroupedMenu() { children.map((item) => item.children ? ( mobile ? ( - - +
+ {t(item.label)} - - {items(item.children)} - + +
+ {items(item.children)} +
+
) : ( @@ -138,6 +145,7 @@ export function PortalGroupedMenu() { 'realization', 'support', 'work', + 'administration', ].includes(group.id), ); const institutionalCurrent = url.split('?')[0] === '/contacto'; @@ -203,6 +211,7 @@ export function PortalGroupedMenu() { 'discovery', 'realization', 'support', + 'administration', ].includes(group.id), ) .map((group) => ( diff --git a/resources/js/lib/portal-navigation.ts b/resources/js/lib/portal-navigation.ts index 26296836..00827565 100644 --- a/resources/js/lib/portal-navigation.ts +++ b/resources/js/lib/portal-navigation.ts @@ -13,6 +13,7 @@ export type PortalNavAccess = { opportunities?: { index: string; create?: string | null }; }; scopedAdministration?: { label: string; href: string }[]; + dashboardNavigation?: { label: string; href: string }[]; procurement?: boolean; funding?: boolean; authenticated: boolean; @@ -477,6 +478,44 @@ export function portalNavigation(access: PortalNavAccess): PortalNavItem[] { ? access.opportunityManagement.create.href : null) : null; + const dashboardActivities = access.authenticated + ? [ + ...(access.dashboardNavigation ?? []), + ...previous + .filter((group) => group.id === 'account') + .flatMap((group) => group.children ?? []) + .filter((item) => item.href === '/dashboard') + .map((item) => ({ + label: 'Painel operacional', + href: item.href!, + })), + ] + : []; + const administration = access.authenticated + ? [ + ...(dashboardActivities.length + ? [ + { + id: 'collaborative-dashboard', + label: 'Dashboard', + children: dashboardActivities.map((item, index) => + link( + 'dashboard-activity-' + index, + item.label, + item.href, + ), + ), + }, + ] + : []), + ...previous + .filter((group) => group.id === 'management') + .flatMap((group) => group.children ?? []), + ...(access.scopedAdministration ?? []).map((item, index) => + link('scoped-' + index, item.label, item.href), + ), + ] + : []; return uniqueNavigationDestinations([ { id: 'discovery', @@ -603,6 +642,15 @@ export function portalNavigation(access: PortalNavAccess): PortalNavItem[] { ], }, + ...(administration.length + ? [ + { + id: 'administration', + label: 'Administração', + children: administration, + }, + ] + : []), { id: 'work', label: 'Área de trabalho', @@ -633,28 +681,8 @@ export function portalNavigation(access: PortalNavAccess): PortalNavItem[] { ] : []), ...previous - .filter((group) => - ['management', 'profile-opportunities'].includes( - group.id, - ), - ) + .filter((group) => group.id === 'profile-opportunities') .flatMap((group) => group.children ?? []), - ...(access.authenticated && access.scopedAdministration?.length - ? [ - { - id: 'work-administration', - label: 'Administração', - children: access.scopedAdministration.map( - (item, index) => - link( - 'scoped-' + index, - item.label, - item.href, - ), - ), - }, - ] - : []), ], }, ...previous.filter((group) => group.id === 'account'), diff --git a/resources/js/pages/management/editorial-workflow.tsx b/resources/js/pages/management/editorial-workflow.tsx index a73de7d0..5d8afa86 100644 --- a/resources/js/pages/management/editorial-workflow.tsx +++ b/resources/js/pages/management/editorial-workflow.tsx @@ -85,81 +85,126 @@ export default function EditorialWorkflow({ }) { const pageUrl = (page: number) => `/management/editorial?${new URLSearchParams({ ...filters, page: String(page) })}`; + const areas = [ + { + title: 'Conteúdos e oportunidades', + entries: [ + { + label: 'Artigos', + href: resourceAccess + ? '/management/editorial?kind=article' + : null, + }, + { + label: 'Legislação', + href: resourceAccess + ? '/management/editorial?kind=legislation' + : null, + }, + { + label: 'Documentos', + href: resourceAccess + ? '/management/editorial?kind=document' + : null, + }, + { + label: 'Oportunidades', + href: opportunitiesUrl, + pending: opportunityPending, + }, + { + label: 'Biblioteca e editores', + href: resourceAccess ? '/management/portal-services' : null, + }, + ], + }, + { + title: 'Investimentos e competitividade', + entries: [ + { + label: 'Carteira', + href: portfolioUrl, + pending: portfolioPending, + }, + { + label: 'Competitividade dos clusters', + href: clustersUrl, + pending: nativePending.clusters, + }, + { + label: 'Estruturas e relações', + href: subjectsUrl, + pending: subjectsPending, + }, + ], + }, + { + title: 'Território e dados', + entries: [ + { + label: 'Conteúdos territoriais', + href: territorialUrl, + pending: nativePending.territories, + }, + { + label: 'Zonas e indicadores', + href: zonesUrl, + pending: nativePending.zones ?? 0, + }, + { + label: 'Demografia', + href: demographyUrl, + pending: demographyPending, + }, + ], + }, + ].filter((area) => area.entries.some((item) => item.href)); return ( - -

- Artigos, legislação e documentos: edições privadas e versão - pública separadas. As oportunidades mantêm o seu circuito - próprio. + +

+ Acompanhe a preparação, revisão e publicação numa única área de + trabalho. Seleccione uma actividade para continuar nas tarefas + da sua equipa.

-
- {zonesUrl && ( - - Zonas e indicadores: {nativePending.zones ?? 0}{' '} - pendentes - - )} - {subjectsUrl && ( - - Estruturas e relações: {subjectsPending} pendentes - - )} - {demographyUrl && ( - - Demografia: {demographyPending} lotes pendentes - - )} - {territorialUrl && ( - - Conteúdos territoriais: {nativePending.territories}{' '} - pendentes - - )} - {portfolioUrl && ( - - Carteira: {portfolioPending} fichas pendentes - - )} - {clustersUrl && ( - - Competitividade dos clusters: {nativePending.clusters}{' '} - pendentes - - )} - - Painel geral - - {resourceAccess && ( - + {areas.map((area) => ( +
- Biblioteca e editores - - )} - {opportunitiesUrl && ( - - Oportunidades - - )} -
- {opportunitiesUrl && ( -
-

Oportunidades

-

- {opportunityPending} conteúdos pendentes no seu âmbito - de acesso. -

- - Continuar na fila de oportunidades existente - -
- )} +

+ {area.title} +

+
    + {area.entries.map((item) => + item.href ? ( +
  • + + {item.label} + {'pending' in item && ( + + {item.pending} pendentes + + )} + +
  • + ) : null, + )} +
+ + ))} + {resourceAccess && ( <> +

+ Fila de conteúdos +

)} -
-

Notificações

+
+

Notificações

+ {notices.length === 0 && ( +

+ Não há notificações neste âmbito. +

+ )} {notices.map((n) => ( -

+

{eventTitles[n.action] ?? 'Alteração editorial'} ·{' '} {n.reference} diff --git a/tests/Feature/EditorialNativeNoticeTest.php b/tests/Feature/EditorialNativeNoticeTest.php index e19157f7..6551f26e 100644 --- a/tests/Feature/EditorialNativeNoticeTest.php +++ b/tests/Feature/EditorialNativeNoticeTest.php @@ -5,6 +5,7 @@ namespace Tests\Feature; use App\Models\EconomicCluster; use App\Models\User; use App\Services\ClusterCompetitiveness; +use App\Services\ContentManagement; use App\Services\PortalEditorialNativeNotices; use App\Services\PortalEditorialWorkflow; use App\Services\TerritorialRegistry; @@ -34,6 +35,19 @@ final class EditorialNativeNoticeTest extends TestCase $this->actingAsWithMfa($this->author); } + public function test_dashboard_navigation_uses_existing_scopes_and_does_not_expose_disabled_or_private_activities(): void + { + $service = app(ContentManagement::class); + $items = $service->dashboardNavigation($this->author, $service->access($this->author)); + $this->assertSame(['label' => 'Visão geral', 'href' => '/management/editorial'], $items[0]); + $this->assertContains('/management/clusters', array_column($items, 'href')); + $outsider = User::factory()->create(); + $this->assertSame([], $service->dashboardNavigation($outsider, [])); + $this->assertSame([], $service->dashboardNavigation(null, [])); + config(['editorial_workflow.enabled' => false]); + $this->assertSame([], $service->dashboardNavigation($this->author, $service->access($this->author))); + } + public function test_cluster_events_use_the_common_inbox_and_recheck_current_scope_and_event_integrity(): void { $cluster = EconomicCluster::create(['code' => 'NOTICE-'.Str::random(12), 'name' => 'Synthetic notice cluster', 'sector' => 'Energy', 'status' => 'planned']); diff --git a/tests/frontend/editorial-dashboard-presentation.test.mjs b/tests/frontend/editorial-dashboard-presentation.test.mjs new file mode 100644 index 00000000..07c64ffa --- /dev/null +++ b/tests/frontend/editorial-dashboard-presentation.test.mjs @@ -0,0 +1,23 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import {createRequire} from 'node:module'; +import React from 'react'; +import {renderToStaticMarkup} from 'react-dom/server'; +import ts from 'typescript'; +const require=createRequire(import.meta.url), module={exports:{}}; +const code=ts.transpileModule(fs.readFileSync(new URL('../../resources/js/pages/management/editorial-workflow.tsx',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,jsx:ts.JsxEmit.ReactJSX}}).outputText; +new Function('require','module','exports',code)(name=>name==='@inertiajs/react'?{Link:({href,children,...props})=>React.createElement('a',{href,...props},children),router:{}}:name==='./shared'?{Management:({title,children})=>React.createElement('main',null,React.createElement('h1',null,title),children),Field:({label,children})=>React.createElement('label',null,label,children),button:'',control:''}:require(name),module,module.exports); +const base={items:{data:[],current_page:1,last_page:1},notices:[],resourceAccess:true,opportunitiesUrl:'/opportunities-qa',opportunityPending:2,clustersUrl:'/clusters-qa',nativePending:{clusters:3,territories:0,zones:0},subjectsUrl:'/subjects-qa',territorialUrl:'/territories-qa',demographyUrl:'/demography-qa'}; +test('dashboard groups authorised activities once with contextual counts and an empty notification state',()=>{ + const html=renderToStaticMarkup(React.createElement(module.exports.default,base)); + for(const title of ['Conteúdos e oportunidades','Investimentos e competitividade','Território e dados'])assert.match(html,new RegExp(title)); + assert.equal((html.match(/href="\/opportunities-qa"/g)??[]).length,1); + assert.match(html,/2 pendentes/);assert.match(html,/3 pendentes/); + assert.match(html,/Não há notificações neste âmbito/); + assert.match(html,/md:grid-cols-2 xl:grid-cols-3/); +}); +test('restricted dashboard omits unavailable groups, private content controls and counters',()=>{ + const html=renderToStaticMarkup(React.createElement(module.exports.default,{items:base.items,notices:[],resourceAccess:false,opportunitiesUrl:null,opportunityPending:0})); + assert.doesNotMatch(html,/Fila de conteúdos|kind=legislation|Biblioteca e editores|Investimentos e competitividade|Território e dados/); +}); diff --git a/tests/frontend/portal-navigation.test.mjs b/tests/frontend/portal-navigation.test.mjs index 6775dd8a..324a3658 100644 --- a/tests/frontend/portal-navigation.test.mjs +++ b/tests/frontend/portal-navigation.test.mjs @@ -238,7 +238,7 @@ void test('work area exposes existing content capability even when the old dashb ); assert.ok(work.children.some((i) => i.href === '/gestao-conteudos')); assert.ok( - work.children.some((i) => i.href === '/management/opportunities'), + destinations(groups.find(g => g.id === 'administration').children).includes('/management/opportunities'), ); }); void test('every previously authorised management/profile destination survives the regrouping exactly once', () => { @@ -276,7 +276,7 @@ void test('every previously authorised management/profile destination survives t assert.equal(after.filter((x) => x === href).length, 1, href); assert.equal(after.length, new Set(after).size); assert.ok( - destinations(groups.find((g) => g.id === 'work').children).includes( + destinations(groups.find((g) => g.id === 'administration').children).includes( '/management/economic-references/lot-01', ), ); @@ -448,3 +448,17 @@ void test('installed concours has its own primary group and preserves public and ); } }); + +void test('administration owns dashboard activities once and remains hidden without authorised destinations', () => { + const dashboardNavigation = [{label: 'Visão geral', href: '/management/editorial'}, {label: 'Legislação', href: '/management/editorial?kind=legislation'}, {label: 'Competitividade', href: '/management/clusters'}]; + const groups = portalNavigation({authenticated: true, manager: true, dashboardNavigation}); + const admin = groups.find(g => g.id === 'administration'); + const dashboard = admin.children.find(g => g.id === 'collaborative-dashboard'); + assert.equal(dashboard.label, 'Dashboard'); + assert.deepEqual(destinations(dashboard.children), dashboardNavigation.map(i => i.href)); + assert.equal(currentNavigationGroup(groups, '/management/editorial?kind=legislation'), 'administration'); + assert.equal(currentNavigationGroup(groups, '/management/clusters/synthetic/edit'), 'administration'); + assert.equal(destinations(groups).filter(h => h === '/management/clusters').length, 1); + assert.equal(portalNavigation({authenticated: false, manager: false, dashboardNavigation}).some(g => g.id === 'administration'), false); + assert.equal(portalNavigation({authenticated: true, manager: false}).some(g => g.id === 'administration'), false); +}); diff --git a/tests/frontend/portal-six-group-menu.test.mjs b/tests/frontend/portal-six-group-menu.test.mjs index 172c9c98..1b11d6ad 100644 --- a/tests/frontend/portal-six-group-menu.test.mjs +++ b/tests/frontend/portal-six-group-menu.test.mjs @@ -192,3 +192,17 @@ test('actual header has exactly the requested six primary entries on desktop and ), ); }); + +test('authorised administration is primary and dashboard is a nested expandable group on desktop and mobile', () => { + for (mobile of [false, true]) { + state = {url: '/management/editorial', props: {auth: {user: {name: 'QA'}, canManagePortal: false}, dashboardNavigation: [{label: 'Visão geral', href: '/management/editorial'}, {label: 'Legislação', href: '/management/editorial?kind=legislation'}]}}; + const html = renderToStaticMarkup(React.createElement(module.exports.PortalGroupedMenu)); + const nav = html.match(/]*aria-label="Áreas do portal"[\s\S]*?<\/nav>/)?.[0]; + assert.ok(nav); + const labels = [...nav.matchAll(/<(?:a|button)[^>]*class="tempo-nav-trigger"[^>]*>([\s\S]*?)<\/(?:a|button)>/g)].map(m => m[1].replace(/<[^>]*>/g, '').replace('Grupo actual', '').trim()); + assert.deepEqual(labels, ['Início', 'Descoberta', 'Realização', 'Suporte', 'Administração', 'Sobre nós', 'Mais']); + assert.equal((nav.match(/href="\/management\/editorial"/g) ?? []).length, 1); + assert.match(nav, /Dashboard/); + if (mobile) assert.match(nav, /]*>Dashboard<\/summary>/); + } +});