diff --git a/portal_api/views.py b/portal_api/views.py index eb2aaa6..0b6e264 100644 --- a/portal_api/views.py +++ b/portal_api/views.py @@ -4099,7 +4099,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet): { "detail": ( "O arquivo não está no formato esperado (Balancete + DRE do Questor). " - "Contate a Integração e Inovação." + "Contate o setor de Inovação." ) }, status=status.HTTP_400_BAD_REQUEST, @@ -4199,7 +4199,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet): if apuracao is not None: apuracao.arquivo.delete(save=False) return Response( - {"detail": "Não foi possível salvar a análise. Contate a Integração e Inovação."}, + {"detail": "Não foi possível salvar a análise. Contate o setor de Inovação."}, status=status.HTTP_400_BAD_REQUEST, ) @@ -4262,7 +4262,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet): { "detail": ( "O arquivo não está no formato esperado (Balancete + DRE do Questor). " - "Contate a Integração e Inovação." + "Contate o setor de Inovação." ) }, status=status.HTTP_400_BAD_REQUEST, @@ -4305,7 +4305,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet): if novo_arquivo_salvo: apuracao.arquivo.delete(save=False) return Response( - {"detail": "Não foi possível reprocessar a análise. Contate a Integração e Inovação."}, + {"detail": "Não foi possível reprocessar a análise. Contate o setor de Inovação."}, status=status.HTTP_400_BAD_REQUEST, ) diff --git a/static/css/dashboard-contabil.css b/static/css/dashboard-contabil.css index 8f42d04..8beea1c 100644 --- a/static/css/dashboard-contabil.css +++ b/static/css/dashboard-contabil.css @@ -67,6 +67,27 @@ border-radius: var(--radius-md); } +.dc-list-pagination { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-3); + margin-top: var(--space-4); +} + +.dc-list-pagination__info { + font-size: 0.85rem; + color: var(--text-muted); +} + +/* Quantidade de registros do histórico, abaixo da paginação. */ +.dc-list-count { + text-align: center; + font-size: 0.8rem; + color: var(--text-muted); + margin-top: var(--space-2); +} + /* Ordenação + filtro "estilo Excel" por coluna do histórico (#dc-list-table) — mesmo mecanismo/classes de .ips-* em importacao-plano-saude.css, só renomeado pro prefixo desta página (não compartilhado entre os dois @@ -251,6 +272,34 @@ flex-shrink: 0; } +/* Barra de progresso de "Nova Análise"/"Reprocessar" — mostrada enquanto o + PDF é processado de forma síncrona no backend (sem fila de tarefas, não há + um percentual real pra reportar); a subida até ~90% é simulada em JS + (pidDcIniciarProgressoSimulado) e só fecha os últimos 10% quando a + resposta volta de verdade, ver dashboard-contabil.js. */ +.dc-progress__bar { + width: 100%; + height: 6px; + border-radius: var(--radius-pill); + background: var(--border-subtle); + overflow: hidden; +} + +.dc-progress__fill { + height: 100%; + width: 0%; + border-radius: inherit; + background: var(--accent); + transition: width 0.2s ease-out; +} + +.dc-progress__label { + margin-top: var(--space-2); + font-size: 0.85rem; + color: var(--text-muted); + text-align: center; +} + /* Log de reprocessamentos anteriores, dentro do modal "Reprocessar análise" — reaproveita .dc-obs-resumo__titulo/__count (mesmo "título + contagem em pílula" já usado no resumo de observações por aba), só a lista em si é diff --git a/static/js/dashboard-contabil.js b/static/js/dashboard-contabil.js index 7a46277..7096895 100644 --- a/static/js/dashboard-contabil.js +++ b/static/js/dashboard-contabil.js @@ -174,6 +174,32 @@ function pidDcEscapeHtml(texto) { return div.innerHTML; } +// Barra de progresso de "Nova Análise"/"Reprocessar" — o processamento roda +// de forma síncrona numa única requisição (sem fila de tarefas), então não +// existe um percentual real pra reportar. Simula a subida: cresce rápido no +// início e desacelera perto de 90%, sem nunca chegar lá sozinha — só +// `completar()` (chamado quando a resposta volta) fecha os últimos 10% e +// segura em 100% por um instante, pra sempre refletir o fim de verdade do +// processamento, nunca um término fake antes da hora. +function pidDcIniciarProgressoSimulado(fillEl) { + let pct = 0; + fillEl.style.width = "0%"; + const intervalId = setInterval(() => { + pct += (90 - pct) * 0.1; + fillEl.style.width = `${pct}%`; + }, 200); + return { + parar() { + clearInterval(intervalId); + }, + async completar() { + clearInterval(intervalId); + fillEl.style.width = "100%"; + await new Promise((resolve) => setTimeout(resolve, 300)); + }, + }; +} + // Posiciona o `` (fixed, ver // dashboard-contabil.css) relativo à viewport, calculado no momento do // hover/foco — nasce acima do gatilho, centralizado; sem espaço acima @@ -463,14 +489,20 @@ document.addEventListener("DOMContentLoaded", async () => { // Ordenação + filtro "estilo Excel" do histórico (#dc-list-table) — mesmo // mecanismo de listSortKey/listSortDir/listFiltros em // importacao-plano-saude.js: client-side, sobre o array já carregado - // (dcListaApuracoes), sem repaginar nem refazer fetch a cada mudança. + // (dcListaApuracoes), sem refazer fetch a cada mudança. A paginação + // (dcListPage/DC_LIST_PAGE_SIZE) também segue o mesmo padrão de + // importacao-plano-saude.js (IPS_LIST_PAGE_SIZE/renderListPagination). let dcListaApuracoes = []; let dcListSortKey = "criado_em"; let dcListSortDir = -1; // null = sem filtro nessa coluna; senão, um Set com os valores marcados no // popup daquela coluna (combinam entre si por E lógico). let dcListFiltros = { empresa: null, competencia: null, status: null, criado_por: null }; + let dcListPage = 1; + const DC_LIST_PAGE_SIZE = 10; const dcListSortHeaders = Array.from(document.querySelectorAll("#dc-list-table th[data-sort]")); + const dcListPagination = document.getElementById("dc-list-pagination"); + const dcListCount = document.getElementById("dc-list-count"); function showView(view) { listView.hidden = view !== "list"; @@ -529,6 +561,18 @@ document.addEventListener("DOMContentLoaded", async () => { renderList(); } + function dcRenderListPagination(totalPaginas) { + if (!dcListPagination) return; + if (totalPaginas <= 1) { + dcListPagination.innerHTML = ""; + return; + } + dcListPagination.innerHTML = ` + + Página ${dcListPage} de ${totalPaginas} + `; + } + function renderList() { const body = document.getElementById("dc-list-body"); const empty = document.getElementById("dc-list-empty"); @@ -543,8 +587,13 @@ document.addEventListener("DOMContentLoaded", async () => { const extrator = PID_DC_LISTA_SORT_VALOR[dcListSortKey]; const ordenadas = [...filtradas].sort((a, b) => dcComparaValoresLista(extrator(a), extrator(b)) * dcListSortDir); + const totalPaginas = Math.max(1, Math.ceil(ordenadas.length / DC_LIST_PAGE_SIZE)); + dcListPage = Math.min(dcListPage, totalPaginas); + const inicio = (dcListPage - 1) * DC_LIST_PAGE_SIZE; + const pagina = ordenadas.slice(inicio, inicio + DC_LIST_PAGE_SIZE); + body.innerHTML = ""; - ordenadas.forEach((apuracao) => { + pagina.forEach((apuracao) => { const tr = document.createElement("tr"); tr.innerHTML = ` ${pidDcEscapeHtml(apuracao.codigo_empresa)} - ${pidDcEscapeHtml(apuracao.nome_empresa)} ${pidDcFontePdfAtipicaBadgeHtml(apuracao.fonte_pdf_atipica)} @@ -579,6 +628,17 @@ document.addEventListener("DOMContentLoaded", async () => { th.classList.toggle("is-sort-asc", key === dcListSortKey && dcListSortDir === 1); th.classList.toggle("is-sort-desc", key === dcListSortKey && dcListSortDir === -1); }); + + dcRenderListPagination(totalPaginas); + + if (dcListCount) { + if (ordenadas.length === 0) { + dcListCount.textContent = ""; + } else { + const fim = Math.min(inicio + DC_LIST_PAGE_SIZE, ordenadas.length); + dcListCount.textContent = `Mostrando ${inicio + 1}–${fim} de ${ordenadas.length} registro${ordenadas.length === 1 ? "" : "s"}`; + } + } } dcListSortHeaders.forEach((th) => { @@ -594,6 +654,7 @@ document.addEventListener("DOMContentLoaded", async () => { dcListSortKey = key; dcListSortDir = 1; } + dcListPage = 1; renderList(); }); }); @@ -728,6 +789,7 @@ document.addEventListener("DOMContentLoaded", async () => { dcListFiltros[campo] = null; fechar(); atualizarBotaoAtivo(); + dcListPage = 1; renderList(); }); @@ -740,6 +802,7 @@ document.addEventListener("DOMContentLoaded", async () => { dcListFiltros[campo] = marcouTudo ? null : new Set(marcados); fechar(); atualizarBotaoAtivo(); + dcListPage = 1; renderList(); }); @@ -756,6 +819,7 @@ document.addEventListener("DOMContentLoaded", async () => { dcListSortDir = -1; dcListFiltros = { empresa: null, competencia: null, status: null, criado_por: null }; dcFiltrosColuna.forEach((f) => f.atualizarBotaoAtivo()); + dcListPage = 1; renderList(); }); } @@ -764,6 +828,18 @@ document.addEventListener("DOMContentLoaded", async () => { if (!event.target.closest(".dc-col-filter")) dcFecharTodosOsFiltrosColuna(); }); + if (dcListPagination) { + dcListPagination.addEventListener("click", (event) => { + if (event.target.closest("#dc-list-prev-btn")) { + dcListPage -= 1; + renderList(); + } else if (event.target.closest("#dc-list-next-btn")) { + dcListPage += 1; + renderList(); + } + }); + } + document.getElementById("dc-list-body").addEventListener("click", async (event) => { const abrirBtn = event.target.closest("[data-dc-abrir]"); if (abrirBtn) { @@ -859,9 +935,15 @@ document.addEventListener("DOMContentLoaded", async () => { formData.append("arquivo", arquivo); const confirmarBtn = document.getElementById("dc-reprocessar-confirmar-btn"); + const cancelarBtn = document.getElementById("dc-reprocessar-cancel-btn"); + const progressEl = document.getElementById("dc-reprocessar-progress"); confirmarBtn.disabled = true; + cancelarBtn.disabled = true; + progressEl.hidden = false; + const progresso = pidDcIniciarProgressoSimulado(progressEl.querySelector(".dc-progress__fill")); try { const atualizada = await pidReprocessarApuracaoContabil(dcReprocessarApuracaoId, formData); + await progresso.completar(); fecharReprocessarModal(); await carregarLista(); // Se a apuração reprocessada é a que já está aberta na tela de @@ -878,9 +960,12 @@ document.addEventListener("DOMContentLoaded", async () => { } await pidAlert("Análise reprocessada com sucesso."); } catch (e) { + progresso.parar(); errorEl.textContent = e.message; } finally { confirmarBtn.disabled = false; + cancelarBtn.disabled = false; + progressEl.hidden = true; } }); @@ -932,9 +1017,17 @@ document.addEventListener("DOMContentLoaded", async () => { formData.append("arquivo", arquivo); const submitBtn = document.getElementById("dc-form-submit-btn"); + const cancelBtn = document.getElementById("dc-form-cancel-btn"); + const backBtn = document.getElementById("dc-form-back-btn"); + const progressEl = document.getElementById("dc-form-progress"); submitBtn.disabled = true; + cancelBtn.disabled = true; + backBtn.disabled = true; + progressEl.hidden = false; + const progresso = pidDcIniciarProgressoSimulado(progressEl.querySelector(".dc-progress__fill")); try { const apuracao = await pidCriarApuracaoContabil(formData); + await progresso.completar(); apuracaoAtual = apuracao; await dcCarregarObservacoes(); filtroSeveridade = "todas"; @@ -944,9 +1037,13 @@ document.addEventListener("DOMContentLoaded", async () => { showView("review"); await carregarLista(); } catch (e) { + progresso.parar(); errorEl.textContent = e.message; } finally { submitBtn.disabled = false; + cancelBtn.disabled = false; + backBtn.disabled = false; + progressEl.hidden = true; } }); diff --git a/templates/dashboard-contabil.html b/templates/dashboard-contabil.html index 10d3efe..5b6dcce 100644 --- a/templates/dashboard-contabil.html +++ b/templates/dashboard-contabil.html @@ -363,6 +363,8 @@ +
+

+