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": (
"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,
)

View File

@ -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 é

View File

@ -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 `<span class="dc-hover-tooltip__texto">` (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 = `
<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() {
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 = `
<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-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;
}
});

View File

@ -363,6 +363,8 @@
</table>
</div>
<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 id="dc-form-view" hidden>
@ -390,6 +392,10 @@
</div>
</div>
<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">
<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>
@ -624,6 +630,10 @@
<ul class="dc-reprocessar-log__lista" id="dc-reprocessar-log-lista"></ul>
</section>
<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">
<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>