Criação da tela de clientes e relatórios
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -4,6 +4,16 @@
|
||||
|
||||
<h1 style="font-size: 1.5rem; margin-bottom: 1rem;">📤 Upload de Faturas</h1>
|
||||
|
||||
<!-- Seletor de Cliente (obrigatório) -->
|
||||
<div style="display:flex; gap:12px; align-items:center; margin: 0 0 14px 0;">
|
||||
<label for="select-cliente" style="font-weight:600;">Cliente:</label>
|
||||
<select id="select-cliente" style="min-width:320px; padding:.6rem .8rem; border:1px solid #ddd; border-radius:10px;">
|
||||
<option value="">— Selecione um cliente —</option>
|
||||
</select>
|
||||
<span id="cliente-aviso" class="muted">Selecione o cliente antes de anexar/ processar.</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="upload-box" id="upload-box">
|
||||
<h3>Arraste faturas em PDF aqui ou clique para selecionar</h3>
|
||||
<p style="color: gray; font-size: 0.9rem;">Apenas PDFs textuais (não escaneados)</p>
|
||||
@@ -34,7 +44,6 @@
|
||||
</div>
|
||||
<div id="tabela-wrapper" class="tabela-wrapper"></div>
|
||||
</div>
|
||||
ar
|
||||
<script>
|
||||
let arquivos = [];
|
||||
let statusInterval = null;
|
||||
@@ -43,18 +52,49 @@ ar
|
||||
|
||||
const fileTable = document.getElementById('file-table');
|
||||
|
||||
function handleFiles(files) {
|
||||
if (processado) {
|
||||
document.getElementById("feedback-sucesso").innerText = "";
|
||||
document.getElementById("feedback-erro").innerText = "⚠️ Conclua ou inicie um novo processo antes de adicionar mais arquivos.";
|
||||
document.getElementById("feedback-duplicado").innerText = "";
|
||||
document.getElementById("upload-feedback").classList.remove("hidden");
|
||||
return;
|
||||
|
||||
// <<< NOVO: carrega clientes ativos no combo
|
||||
async function carregarClientes() {
|
||||
try {
|
||||
const r = await fetch('/api/clientes'); // se quiser só ativos: /api/clientes?ativos=true
|
||||
if (!r.ok) throw new Error('Falha ao carregar clientes');
|
||||
const lista = await r.json();
|
||||
|
||||
const sel = document.getElementById('select-cliente');
|
||||
sel.innerHTML = `<option value="">— Selecione um cliente —</option>` +
|
||||
lista.map(c => `<option value="${c.id}">${c.nome_fantasia}${c.cnpj ? ' — ' + c.cnpj : ''}</option>`).join('');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Não foi possível carregar a lista de clientes.');
|
||||
}
|
||||
}
|
||||
|
||||
function clienteSelecionado() {
|
||||
return (document.getElementById('select-cliente')?.value || '').trim();
|
||||
}
|
||||
|
||||
// <<< AJUSTE: impedir anexar sem cliente
|
||||
function handleFiles(files) {
|
||||
if (!clienteSelecionado()) {
|
||||
alert('Selecione um cliente antes de anexar os arquivos.');
|
||||
return;
|
||||
}
|
||||
if (processado) {
|
||||
document.getElementById("feedback-sucesso").innerText = "";
|
||||
document.getElementById("feedback-erro").innerText = "⚠️ Conclua ou inicie um novo processo antes de adicionar mais arquivos.";
|
||||
document.getElementById("feedback-duplicado").innerText = "";
|
||||
document.getElementById("upload-feedback").classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
arquivos = [...arquivos, ...files];
|
||||
|
||||
// trava o combo após começar a anexar (opcional)
|
||||
document.getElementById('select-cliente').disabled = true;
|
||||
|
||||
renderTable();
|
||||
}
|
||||
|
||||
arquivos = [...arquivos, ...files];
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function renderTable(statusList = []) {
|
||||
const grupos = ['Aguardando', 'Enviado', 'Erro', 'Duplicado'];
|
||||
@@ -102,7 +142,20 @@ function renderTable(statusList = []) {
|
||||
}
|
||||
|
||||
async function processar(btn) {
|
||||
if (!clienteSelecionado()) {
|
||||
alert("Selecione um cliente antes de processar.");
|
||||
return;
|
||||
}
|
||||
if (arquivos.length === 0) return alert("Nenhum arquivo selecionado.");
|
||||
|
||||
// Confirmação
|
||||
const clienteTxt = document.querySelector('#select-cliente option:checked')?.textContent || '';
|
||||
if (!confirm(`Confirmar processamento de ${arquivos.length} arquivo(s) para o cliente:\n\n${clienteTxt}`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clienteId = clienteSelecionado();
|
||||
|
||||
document.getElementById("tabela-wrapper").classList.add("bloqueada");
|
||||
|
||||
if (processamentoFinalizado) {
|
||||
@@ -120,8 +173,9 @@ async function processar(btn) {
|
||||
document.getElementById("barra-progresso").style.width = "0%";
|
||||
|
||||
for (let i = 0; i < total; i++) {
|
||||
const file = arquivos[i];
|
||||
const file = arquivos[i]; // <- declare 'file' ANTES de usar
|
||||
const formData = new FormData();
|
||||
formData.append("cliente_id", clienteId); // <- usa o cache do cliente
|
||||
formData.append("files", file);
|
||||
|
||||
// Atualiza status visual antes do envio
|
||||
@@ -129,14 +183,16 @@ async function processar(btn) {
|
||||
nome: file.name,
|
||||
status: "Enviando...",
|
||||
mensagem: `(${i + 1}/${total})`,
|
||||
tempo: "---"
|
||||
tempo: "---",
|
||||
tamanho: (file.size / 1024).toFixed(1) + " KB",
|
||||
data: new Date(file.lastModified).toLocaleDateString()
|
||||
});
|
||||
renderTable(statusList);
|
||||
|
||||
const start = performance.now();
|
||||
try {
|
||||
await fetch("/upload-files", { method: "POST", body: formData });
|
||||
let progresso = Math.round(((i + 1) / total) * 100);
|
||||
const progresso = Math.round(((i + 1) / total) * 100);
|
||||
document.getElementById("barra-progresso").style.width = `${progresso}%`;
|
||||
|
||||
statusList[i].status = "Enviado";
|
||||
@@ -147,9 +203,7 @@ async function processar(btn) {
|
||||
}
|
||||
|
||||
renderTable(statusList);
|
||||
|
||||
// Delay de 200ms entre cada envio
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
await new Promise(r => setTimeout(r, 200)); // pequeno delay
|
||||
}
|
||||
|
||||
btn.innerText = "⏳ Iniciando processamento...";
|
||||
@@ -206,23 +260,39 @@ async function processar(btn) {
|
||||
}
|
||||
}
|
||||
|
||||
function limpar() {
|
||||
fetch("/clear-all", { method: "POST" });
|
||||
arquivos = [];
|
||||
processado = false;
|
||||
document.getElementById("file-input").value = null;
|
||||
renderTable();
|
||||
function limpar() {
|
||||
fetch("/clear-all", { method: "POST" });
|
||||
|
||||
// limpa feedback visual também
|
||||
document.getElementById("upload-feedback").classList.add("hidden");
|
||||
document.getElementById("feedback-sucesso").innerText = "";
|
||||
document.getElementById("feedback-erro").innerText = "";
|
||||
document.getElementById("feedback-duplicado").innerText = "";
|
||||
document.getElementById("tabela-wrapper").classList.remove("bloqueada");
|
||||
processamentoFinalizado = false;
|
||||
document.getElementById("btn-selecionar").disabled = false;
|
||||
// reset da fila/estado
|
||||
arquivos = [];
|
||||
processado = false;
|
||||
processamentoFinalizado = false;
|
||||
if (statusInterval) { clearInterval(statusInterval); statusInterval = null; }
|
||||
|
||||
// reset dos inputs/visual
|
||||
document.getElementById("file-input").value = null;
|
||||
document.getElementById("tabela-wrapper").classList.remove("bloqueada");
|
||||
document.getElementById("overlay-bloqueio").classList.add("hidden");
|
||||
document.getElementById("barra-progresso").style.width = "0%";
|
||||
document.getElementById("btn-selecionar").disabled = false;
|
||||
|
||||
// 🔓 permitir mudar o cliente novamente
|
||||
const sel = document.getElementById("select-cliente");
|
||||
sel.disabled = false;
|
||||
sel.value = ""; // <- se NÃO quiser limpar a escolha anterior, remova esta linha
|
||||
document.getElementById("cliente-aviso").textContent =
|
||||
"Selecione o cliente antes de anexar/ processar.";
|
||||
|
||||
// limpar feedback
|
||||
document.getElementById("upload-feedback").classList.add("hidden");
|
||||
document.getElementById("feedback-sucesso").innerText = "";
|
||||
document.getElementById("feedback-erro").innerText = "";
|
||||
document.getElementById("feedback-duplicado").innerText = "";
|
||||
|
||||
// limpar tabela
|
||||
renderTable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function baixarPlanilha() {
|
||||
window.open('/export-excel', '_blank');
|
||||
@@ -233,6 +303,7 @@ async function processar(btn) {
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
carregarClientes();
|
||||
updateStatus();
|
||||
|
||||
const dragOverlay = document.getElementById("drag-overlay");
|
||||
@@ -255,11 +326,15 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener("drop", e => {
|
||||
e.preventDefault();
|
||||
dragOverlay.classList.remove("active");
|
||||
dragCounter = 0;
|
||||
handleFiles(e.dataTransfer.files);
|
||||
window.addEventListener("drop", e => {
|
||||
e.preventDefault();
|
||||
dragOverlay.classList.remove("active");
|
||||
dragCounter = 0;
|
||||
if (!clienteSelecionado()) {
|
||||
alert('Selecione um cliente antes de anexar os arquivos.');
|
||||
return;
|
||||
}
|
||||
handleFiles(e.dataTransfer.files);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user