Alterações visuais das tabelas - Relatório Contábil

This commit is contained in:
Gabriel 2026-09-21 17:00:26 -03:00
parent 7651ae23be
commit fcd5a70579
4 changed files with 162 additions and 6 deletions

View File

@ -4099,7 +4099,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
{ {
"detail": ( "detail": (
"O arquivo não está no formato esperado (Balancete + DRE do Questor). " "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, status=status.HTTP_400_BAD_REQUEST,
@ -4199,7 +4199,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
if apuracao is not None: if apuracao is not None:
apuracao.arquivo.delete(save=False) apuracao.arquivo.delete(save=False)
return Response( 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, status=status.HTTP_400_BAD_REQUEST,
) )
@ -4262,7 +4262,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
{ {
"detail": ( "detail": (
"O arquivo não está no formato esperado (Balancete + DRE do Questor). " "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, status=status.HTTP_400_BAD_REQUEST,
@ -4305,7 +4305,7 @@ class ContabilApuracaoViewSet(viewsets.ModelViewSet):
if novo_arquivo_salvo: if novo_arquivo_salvo:
apuracao.arquivo.delete(save=False) apuracao.arquivo.delete(save=False)
return Response( 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, status=status.HTTP_400_BAD_REQUEST,
) )

View File

@ -67,6 +67,27 @@
border-radius: var(--radius-md); 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) /* Ordenação + filtro "estilo Excel" por coluna do histórico (#dc-list-table)
— mesmo mecanismo/classes de .ips-* em importacao-plano-saude.css, só — mesmo mecanismo/classes de .ips-* em importacao-plano-saude.css, só
renomeado pro prefixo desta página (não compartilhado entre os dois renomeado pro prefixo desta página (não compartilhado entre os dois
@ -251,6 +272,34 @@
flex-shrink: 0; 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" /* Log de reprocessamentos anteriores, dentro do modal "Reprocessar análise"
— reaproveita .dc-obs-resumo__titulo/__count (mesmo "título + contagem em — 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 é pílula" já usado no resumo de observações por aba), só a lista em si é

View File

@ -174,6 +174,32 @@ function pidDcEscapeHtml(texto) {
return div.innerHTML; 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 `<span class="dc-hover-tooltip__texto">` (fixed, ver // Posiciona o `<span class="dc-hover-tooltip__texto">` (fixed, ver
// dashboard-contabil.css) relativo à viewport, calculado no momento do // dashboard-contabil.css) relativo à viewport, calculado no momento do
// hover/foco — nasce acima do gatilho, centralizado; sem espaço acima // 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 // Ordenação + filtro "estilo Excel" do histórico (#dc-list-table) — mesmo
// mecanismo de listSortKey/listSortDir/listFiltros em // mecanismo de listSortKey/listSortDir/listFiltros em
// importacao-plano-saude.js: client-side, sobre o array já carregado // 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 dcListaApuracoes = [];
let dcListSortKey = "criado_em"; let dcListSortKey = "criado_em";
let dcListSortDir = -1; let dcListSortDir = -1;
// null = sem filtro nessa coluna; senão, um Set com os valores marcados no // null = sem filtro nessa coluna; senão, um Set com os valores marcados no
// popup daquela coluna (combinam entre si por E lógico). // popup daquela coluna (combinam entre si por E lógico).
let dcListFiltros = { empresa: null, competencia: null, status: null, criado_por: null }; 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 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) { function showView(view) {
listView.hidden = view !== "list"; listView.hidden = view !== "list";
@ -529,6 +561,18 @@ document.addEventListener("DOMContentLoaded", async () => {
renderList(); renderList();
} }
function dcRenderListPagination(totalPaginas) {
if (!dcListPagination) return;
if (totalPaginas <= 1) {
dcListPagination.innerHTML = "";
return;
}
dcListPagination.innerHTML = `
<button type="button" class="btn-outline" id="dc-list-prev-btn" ${dcListPage === 1 ? "disabled" : ""}>Página anterior</button>
<span class="dc-list-pagination__info">Página ${dcListPage} de ${totalPaginas}</span>
<button type="button" class="btn-outline" id="dc-list-next-btn" ${dcListPage === totalPaginas ? "disabled" : ""}>Próxima página</button>`;
}
function renderList() { function renderList() {
const body = document.getElementById("dc-list-body"); const body = document.getElementById("dc-list-body");
const empty = document.getElementById("dc-list-empty"); const empty = document.getElementById("dc-list-empty");
@ -543,8 +587,13 @@ document.addEventListener("DOMContentLoaded", async () => {
const extrator = PID_DC_LISTA_SORT_VALOR[dcListSortKey]; const extrator = PID_DC_LISTA_SORT_VALOR[dcListSortKey];
const ordenadas = [...filtradas].sort((a, b) => dcComparaValoresLista(extrator(a), extrator(b)) * dcListSortDir); 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 = ""; body.innerHTML = "";
ordenadas.forEach((apuracao) => { pagina.forEach((apuracao) => {
const tr = document.createElement("tr"); const tr = document.createElement("tr");
tr.innerHTML = ` tr.innerHTML = `
<td>${pidDcEscapeHtml(apuracao.codigo_empresa)} - ${pidDcEscapeHtml(apuracao.nome_empresa)} ${pidDcFontePdfAtipicaBadgeHtml(apuracao.fonte_pdf_atipica)}</td> <td>${pidDcEscapeHtml(apuracao.codigo_empresa)} - ${pidDcEscapeHtml(apuracao.nome_empresa)} ${pidDcFontePdfAtipicaBadgeHtml(apuracao.fonte_pdf_atipica)}</td>
@ -579,6 +628,17 @@ document.addEventListener("DOMContentLoaded", async () => {
th.classList.toggle("is-sort-asc", key === dcListSortKey && dcListSortDir === 1); th.classList.toggle("is-sort-asc", key === dcListSortKey && dcListSortDir === 1);
th.classList.toggle("is-sort-desc", 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) => { dcListSortHeaders.forEach((th) => {
@ -594,6 +654,7 @@ document.addEventListener("DOMContentLoaded", async () => {
dcListSortKey = key; dcListSortKey = key;
dcListSortDir = 1; dcListSortDir = 1;
} }
dcListPage = 1;
renderList(); renderList();
}); });
}); });
@ -728,6 +789,7 @@ document.addEventListener("DOMContentLoaded", async () => {
dcListFiltros[campo] = null; dcListFiltros[campo] = null;
fechar(); fechar();
atualizarBotaoAtivo(); atualizarBotaoAtivo();
dcListPage = 1;
renderList(); renderList();
}); });
@ -740,6 +802,7 @@ document.addEventListener("DOMContentLoaded", async () => {
dcListFiltros[campo] = marcouTudo ? null : new Set(marcados); dcListFiltros[campo] = marcouTudo ? null : new Set(marcados);
fechar(); fechar();
atualizarBotaoAtivo(); atualizarBotaoAtivo();
dcListPage = 1;
renderList(); renderList();
}); });
@ -756,6 +819,7 @@ document.addEventListener("DOMContentLoaded", async () => {
dcListSortDir = -1; dcListSortDir = -1;
dcListFiltros = { empresa: null, competencia: null, status: null, criado_por: null }; dcListFiltros = { empresa: null, competencia: null, status: null, criado_por: null };
dcFiltrosColuna.forEach((f) => f.atualizarBotaoAtivo()); dcFiltrosColuna.forEach((f) => f.atualizarBotaoAtivo());
dcListPage = 1;
renderList(); renderList();
}); });
} }
@ -764,6 +828,18 @@ document.addEventListener("DOMContentLoaded", async () => {
if (!event.target.closest(".dc-col-filter")) dcFecharTodosOsFiltrosColuna(); 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) => { document.getElementById("dc-list-body").addEventListener("click", async (event) => {
const abrirBtn = event.target.closest("[data-dc-abrir]"); const abrirBtn = event.target.closest("[data-dc-abrir]");
if (abrirBtn) { if (abrirBtn) {
@ -859,9 +935,15 @@ document.addEventListener("DOMContentLoaded", async () => {
formData.append("arquivo", arquivo); formData.append("arquivo", arquivo);
const confirmarBtn = document.getElementById("dc-reprocessar-confirmar-btn"); 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; confirmarBtn.disabled = true;
cancelarBtn.disabled = true;
progressEl.hidden = false;
const progresso = pidDcIniciarProgressoSimulado(progressEl.querySelector(".dc-progress__fill"));
try { try {
const atualizada = await pidReprocessarApuracaoContabil(dcReprocessarApuracaoId, formData); const atualizada = await pidReprocessarApuracaoContabil(dcReprocessarApuracaoId, formData);
await progresso.completar();
fecharReprocessarModal(); fecharReprocessarModal();
await carregarLista(); await carregarLista();
// Se a apuração reprocessada é a que já está aberta na tela de // 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."); await pidAlert("Análise reprocessada com sucesso.");
} catch (e) { } catch (e) {
progresso.parar();
errorEl.textContent = e.message; errorEl.textContent = e.message;
} finally { } finally {
confirmarBtn.disabled = false; confirmarBtn.disabled = false;
cancelarBtn.disabled = false;
progressEl.hidden = true;
} }
}); });
@ -932,9 +1017,17 @@ document.addEventListener("DOMContentLoaded", async () => {
formData.append("arquivo", arquivo); formData.append("arquivo", arquivo);
const submitBtn = document.getElementById("dc-form-submit-btn"); 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; submitBtn.disabled = true;
cancelBtn.disabled = true;
backBtn.disabled = true;
progressEl.hidden = false;
const progresso = pidDcIniciarProgressoSimulado(progressEl.querySelector(".dc-progress__fill"));
try { try {
const apuracao = await pidCriarApuracaoContabil(formData); const apuracao = await pidCriarApuracaoContabil(formData);
await progresso.completar();
apuracaoAtual = apuracao; apuracaoAtual = apuracao;
await dcCarregarObservacoes(); await dcCarregarObservacoes();
filtroSeveridade = "todas"; filtroSeveridade = "todas";
@ -944,9 +1037,13 @@ document.addEventListener("DOMContentLoaded", async () => {
showView("review"); showView("review");
await carregarLista(); await carregarLista();
} catch (e) { } catch (e) {
progresso.parar();
errorEl.textContent = e.message; errorEl.textContent = e.message;
} finally { } finally {
submitBtn.disabled = false; submitBtn.disabled = false;
cancelBtn.disabled = false;
backBtn.disabled = false;
progressEl.hidden = true;
} }
}); });

View File

@ -363,6 +363,8 @@
</table> </table>
</div> </div>
<p class="dc-empty" id="dc-list-empty" hidden>Nenhuma análise realizada ainda.</p> <p class="dc-empty" id="dc-list-empty" hidden>Nenhuma análise realizada ainda.</p>
<div class="dc-list-pagination" id="dc-list-pagination"></div>
<p class="dc-list-count" id="dc-list-count"></p>
</div> </div>
<div id="dc-form-view" hidden> <div id="dc-form-view" hidden>
@ -390,6 +392,10 @@
</div> </div>
</div> </div>
<p class="modal-error" id="dc-form-error"></p> <p class="modal-error" id="dc-form-error"></p>
<div class="dc-progress" id="dc-form-progress" hidden>
<div class="dc-progress__bar"><div class="dc-progress__fill"></div></div>
<p class="dc-progress__label">Processando o arquivo, aguarde...</p>
</div>
<div class="modal-actions"> <div class="modal-actions">
<button type="button" class="btn-outline" id="dc-form-cancel-btn">Cancelar</button> <button type="button" class="btn-outline" id="dc-form-cancel-btn">Cancelar</button>
<button type="button" class="btn-solid" id="dc-form-submit-btn">Processar</button> <button type="button" class="btn-solid" id="dc-form-submit-btn">Processar</button>
@ -624,6 +630,10 @@
<ul class="dc-reprocessar-log__lista" id="dc-reprocessar-log-lista"></ul> <ul class="dc-reprocessar-log__lista" id="dc-reprocessar-log-lista"></ul>
</section> </section>
<p class="modal-error" id="dc-reprocessar-error"></p> <p class="modal-error" id="dc-reprocessar-error"></p>
<div class="dc-progress" id="dc-reprocessar-progress" hidden>
<div class="dc-progress__bar"><div class="dc-progress__fill"></div></div>
<p class="dc-progress__label">Processando o arquivo, aguarde...</p>
</div>
<div class="modal-actions"> <div class="modal-actions">
<button type="button" class="btn-outline" id="dc-reprocessar-cancel-btn">Cancelar</button> <button type="button" class="btn-outline" id="dc-reprocessar-cancel-btn">Cancelar</button>
<button type="button" class="btn-solid" id="dc-reprocessar-confirmar-btn">Reprocessar</button> <button type="button" class="btn-solid" id="dc-reprocessar-confirmar-btn">Reprocessar</button>