Implementação de compatibilidade HTTP

This commit is contained in:
Gabriel 2026-09-10 08:53:06 -03:00
parent 668f2eb26b
commit 4676412325
3 changed files with 36 additions and 1 deletions

View File

@ -62,3 +62,9 @@ Usuário reportou erro genérico ao tentar reprocessar os mesmos dois arquivos "
Causa raiz: `_aplica_upsert_nao_conformidades` (rodada 88) fazia `update_or_create`/`get_or_create` **um item por vez** — aceitável nos ~300 registros da amostra inicial (~2 idas ao banco por item), mas o export real de "com finalizadas" tem ~1700 ocorrências/~3400 ações/~9000 acompanhamentos — quase 28 mil idas ao banco. Reescrito pra lote: pré-carrega o que já existe (poucos `SELECT ... WHERE codigo IN (...)`) e aplica tudo de uma vez (`bulk_create`/`bulk_update` por model, `batch_size=500`) — e, mais importante pro caso real de reimportação periódica, **pula por completo quem não mudou nada** desde a última importação (reimportar o Sigsistem inteiro todo mês traz de volta o histórico completo, não só o que é novo). Resultado medido com os arquivos reais: primeira carga ~21s (a maioria virando "atualizada" pela primeira vez), reimportação do mesmo arquivo sem nenhuma mudança real caiu de ~60s pra **~4s** (a maior parte disso é upload do arquivo de 23MB + parsing, não mais banco).
Efeito colateral documentado (não é regressão, é a semântica correta): `resumo["ocorrencias_atualizadas"]`/`["acoes_atualizadas"]` agora contam só quem teve algum campo realmente alterado, não "já existia e foi vista de novo". Nenhuma migração, nenhuma mudança de contrato de API — só a implementação interna do upsert. Detalhe técnico em `portal_api/nao_conformidades/CLAUDE.md`, seção "Upsert".
### Rodada 107 — "Não foi possível copiar o texto." no botão Copiar do Resumo (produção HTTP)
Usuário reportou o aviso "Não foi possível copiar o texto." ao clicar em "Copiar" no popover do Resumo de Gestão, em produção. Causa: `navigator.clipboard.writeText()` (única chamada à Clipboard API no Portal) só funciona em contexto seguro (HTTPS ou `localhost`) — a produção deste Portal roda em HTTP puro hoje, sem TLS, e não há previsão de migrar pra HTTPS no momento (decisão de infraestrutura fora do escopo desta aplicação).
Correção: `resumoCopiarBtn` agora chama `pidNcfCopiarTexto()`, que usa `navigator.clipboard.writeText()` quando `window.isSecureContext` é verdadeiro e, senão, cai num fallback com `document.execCommand("copy")` via um `<textarea>` temporário fora da tela (`pidNcfCopiarTextoFallback`). Sem migração, sem mudança de contrato de API — só o handler de clique em `nao-conformidades.js`. Detalhe em `portal_api/nao_conformidades/CLAUDE.md`, seção "Frontend".

View File

@ -95,6 +95,7 @@ Uma página com 3 abas (`.pa-tabs`, reaproveitado de `perfis-acesso.css`): **Das
- **Filtro combinável de Ações**: duas fileiras de chips independentes, Prazo (`?status_prazo=`) e Tratativa (`?status_tratativa=`), combinam com AND (dois `<select>` de fato seriam mais familiares, mas o padrão chip já é o mesmo do período do Dashboard). A tabela mostra as duas dimensões em colunas separadas ("Prazo" e "Tratativa") — nunca juntar num "Status" só, é exatamente essa mistura que gerou confusão antes.
- **Expandir linha** (`pidNcfCriarLinhaExpandivel`, mesmo padrão de `.cc-painel-fiscal__toggle` de `custo-contratacao.css`/`.js` — chevron que gira via `[aria-expanded="true"] svg{transform:rotate(90deg)}`) é o único mecanismo de "ver mais" das 3 listas: ocorrência (relato completo, causa, cliente, riscos etc. — parte já vem da própria listagem, o resto é buscado lazy em `GET /ocorrencias/{id}/`) e ação (texto completo + histórico de acompanhamentos, mais recente primeiro, 3 visíveis + "Mostrar mais", buscado lazy em `GET /acoes/{id}/`). Não usar modal pra isso — o modal de histórico existiu numa rodada anterior e foi removido a pedido do usuário (ficava colado no botão "Marcar como tratado").
- **Busca compartilhada da Gestão** (`#ncf-gestao-search`): filtra as 3 listas ao mesmo tempo, sempre client-side sobre os dados já carregados (sem round-trip) — colaborador/responsável/emissor/cliente/assunto.
- **Botão "Copiar" do Resumo de Gestão** (`resumoCopiarBtn`): `navigator.clipboard.writeText()` só funciona em contexto seguro (HTTPS/`localhost`) — como a produção deste Portal roda em HTTP puro (sem TLS, decisão de infraestrutura fora do escopo desta aplicação), `pidNcfCopiarTexto()` cai num fallback com `document.execCommand("copy")` (via um `<textarea>` temporário fora da tela) sempre que `navigator.clipboard`/`window.isSecureContext` não estiverem disponíveis. Único uso de Clipboard API no Portal hoje — se o ambiente ganhar HTTPS no futuro, o fallback deixa de ser exercitado sozinho (a checagem de `isSecureContext` já prioriza a API nativa), não precisa remover nada.
Reaproveita `.status-pill` (definido em `perfis-acesso.css`) com modificadores novos (`--ok`/`--danger`/`--warning`/`--neutral`, em `nao-conformidades.css`) em vez de inventar um componente de badge próprio. Chips de filtro/período (`.ncf-chip`/`.ncf-chip-row`) duplicam o visual de `.ind-departamento-chip` (`indicador-desempenho.css`) porque essa folha não é carregada nesta página.

View File

@ -942,9 +942,37 @@ document.addEventListener("DOMContentLoaded", async () => {
resumoBtn.setAttribute("aria-expanded", String(!jaAberto));
});
function pidNcfCopiarTextoFallback(texto) {
const textarea = document.createElement("textarea");
textarea.value = texto;
textarea.style.position = "fixed";
textarea.style.top = "-1000px";
textarea.style.left = "-1000px";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
let ok = false;
try {
ok = document.execCommand("copy");
} catch (e) {
ok = false;
}
document.body.removeChild(textarea);
return ok;
}
async function pidNcfCopiarTexto(texto) {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(texto);
return true;
}
return pidNcfCopiarTextoFallback(texto);
}
resumoCopiarBtn.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(resumoTextoAtual);
const copiado = await pidNcfCopiarTexto(resumoTextoAtual);
if (!copiado) throw new Error("copy fallback failed");
const textoOriginal = resumoCopiarBtn.textContent;
resumoCopiarBtn.textContent = "Copiado!";
setTimeout(() => {