Atualização: template Excel de alíquotas e layout da aba
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-07-30 09:48:44 -03:00
parent b51eeac014
commit d8db2a60e5
8 changed files with 976 additions and 198 deletions

View File

@@ -9,15 +9,16 @@
<div class="tabs">
<button class="tab active" onclick="switchTab('formulas')">📄 Fórmulas</button>
<button class="tab" onclick="switchTab('selic')">📊 Gestão SELIC</button>
<button class="tab" onclick="switchTab('aliquotas')">🧾 Cadastro de Alíquotas por Estado</button>
</div>
<!-- ABA FÓRMULAS -->
<div id="formulas" class="tab-content active">
<form method="post" class="formulario-box">
<div class="grid">
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));">
<div class="form-group">
<label for="tipo">Tipo:</label>
<input type="text" name="tipo" id="tipo" value="{{ parametros.tipo or '' }}" required />
<label for="nome">Nome:</label>
<input type="text" name="nome" id="nome" value="{{ parametros.nome or '' }}" required />
</div>
<div class="form-group">
<label for="aliquota_icms">Alíquota de ICMS (%):</label>
@@ -28,14 +29,23 @@
<div class="form-group">
<label for="formula">Fórmula:</label>
<div class="editor-box">
<select onchange="inserirCampo(this)">
<option value="">Inserir campo...</option>
<option value="pis_base">pis_base</option>
<option value="cofins_base">cofins_base</option>
<option value="icms_valor">icms_valor</option>
<option value="pis_aliq">pis_aliq</option>
<option value="cofins_aliq">cofins_aliq</option>
</select>
<div style="margin-bottom: 0.5rem;">
<strong>Campos disponíveis:</strong>
<div class="campo-badges">
{% for campo in campos_fatura %}
<span class="badge-campo" onclick="inserirNoEditor('{{ campo }}')">{{ campo }}</span>
{% endfor %}
</div>
</div>
<div style="margin-bottom: 0.5rem;">
<strong>Operadores:</strong>
<div class="campo-badges">
{% for op in ['+', '-', '*', '/', '(', ')'] %}
<span class="badge-operador" onclick="inserirNoEditor('{{ op }}')">{{ op }}</span>
{% endfor %}
</div>
</div>
<textarea name="formula" id="formula" rows="3" required>{{ parametros.formula or '' }}</textarea>
<div class="actions-inline">
<button type="button" class="btn btn-secondary" onclick="testarFormula()">🧪 Testar Fórmula</button>
@@ -45,64 +55,149 @@
</div>
</div>
<div class="form-group check-group">
<label><input type="checkbox" name="incluir_icms" value="1" {% if parametros.incluir_icms %}checked{% endif %}> Incluir ICMS</label>
<label><input type="checkbox" name="incluir_pis" value="1" {% if parametros.incluir_pis %}checked{% endif %}> Incluir PIS</label>
<label><input type="checkbox" name="incluir_cofins" value="1" {% if parametros.incluir_cofins %}checked{% endif %}> Incluir COFINS</label>
<label><input type="checkbox" name="ativo" value="1" {% if parametros.ativo %}checked{% endif %}> Ativo</label>
</div>
<button type="submit" class="btn btn-primary">💾 Salvar Parâmetro</button>
</form>
<button type="button" class="btn btn-primary pulse" onclick="limparFormulario()">🔁 Novo Parâmetro</button>
<h3 style="margin-top: 2rem;">📋 Fórmulas Salvas</h3>
</form>
<hr style="margin-top: 2rem; margin-bottom: 1rem;">
<h3 style="margin-top: 2rem;">📋 Fórmulas Salvas</h3>
<div class="card-list">
{% for param in lista_parametros %}
<div class="param-card">
<h4>{{ param.tipo }}</h4>
<code>{{ param.formula }}</code>
<div class="actions">
<a href="?editar={{ param.id }}" class="btn btn-sm">✏️ Editar</a>
<a href="?testar={{ param.id }}" class="btn btn-sm btn-secondary">🧪 Testar</a>
<a href="/parametros/delete/{{ param.id }}" class="btn btn-sm btn-danger">🗑️ Excluir</a>
<div class="param-card {{ 'ativo' if param.ativo else 'inativo' }}" id="card-{{ param.id }}">
<div style="display:flex; justify-content:space-between; align-items:center;">
<input type="text" class="edit-nome" value="{{ param.nome }}" data-id="{{ param.id }}"
onkeydown="if(event.key==='Enter'){ event.preventDefault(); salvarInline('{{ param.id }}') }" />
<span class="badge-status">{{ 'Ativo ✅' if param.ativo else 'Inativo ❌' }}</span>
</div>
<textarea class="edit-formula" data-id="{{ param.id }}" title="{{ param.formula }}">{{ param.formula }}</textarea>
<!-- botão de testar e salvar -->
<div style="display: flex; justify-content: space-between; align-items:center;">
<label>
<input type="checkbox" class="toggle-ativo" data-id="{{ param.id }}" {% if param.ativo %}checked{% endif %}>
Ativo
</label>
<div class="actions">
<button type="button" class="btn btn-sm btn-secondary btn-testar" data-id="{{ param.id }}">🧪 Testar</button>
<button class="btn btn-sm btn-primary" onclick="salvarInline('{{ param.id }}')">💾 Salvar</button>
<a href="/parametros/delete/{{ param.id }}" class="btn btn-sm btn-danger" onclick="return confirm('Deseja excluir?')">🗑️ Excluir</a>
</div>
</div>
<div class="mensagem-info" id="resultado-inline-{{ param.id }}" style="margin-top: 0.5rem; display:none;"></div>
</div>
</div>
{% else %}<p style="color:gray;">Nenhuma fórmula cadastrada.</p>{% endfor %}
{% else %}
<p style="color:gray;">Nenhuma fórmula cadastrada.</p>
{% endfor %}
</div>
</div>
<!-- ABA SELIC -->
<div id="selic" class="tab-content" style="display:none;">
<div id="selic" class="tab-content">
<div class="formulario-box">
<h3>📈 Gestão da SELIC</h3>
<p>Utilize o botão abaixo para importar os fatores SELIC automaticamente a partir da API do Banco Central.</p>
<form method="post" action="/parametros/selic/importar">
<form method="post" action="/parametros/selic/importar" onsubmit="mostrarLoadingSelic()">
<div class="form-group">
<label for="data_maxima">Data máxima para cálculo da SELIC:</label>
<input type="date" id="data_maxima" name="data_maxima" value="{{ data_maxima or '' }}" />
</div>
<button type="submit" class="btn btn-primary">⬇️ Atualizar Fatores SELIC</button>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<button type="submit" class="btn btn-primary">⬇️ Atualizar Fatores SELIC</button>
<button type="button" class="btn btn-secondary" onclick="mostrarFeedback('🔁 Atualização', 'Função de recarga futura')">🔄 Recarregar</button>
</div>
<div class="mensagem-info" style="margin-top:1rem;">Última data coletada da SELIC: <strong>{{ ultima_data_selic }}</strong></div>
</form>
<div class="mensagem-info" style="margin-top:1rem;">Última data coletada da SELIC: <strong>{{ ultima_data_selic }}</strong></div>
<table class="selic-table">
<thead><tr><th>Competência</th><th>Fator</th></tr></thead>
<tbody>
{% for item in selic_dados %}
<tr><td>{{ item.competencia }}</td><td>{{ item.fator }}</td></tr>
<tr>
<td>{{ "%02d"|format(item.mes) }}/{{ item.ano }}</td>
<td>{{ "%.4f"|format(item.percentual) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- ABA ALÍQUOTAS -->
<div id="aliquotas" class="tab-content">
<div class="formulario-box">
<form onsubmit="return salvarAliquota(this)">
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));">
<div class="form-group">
<label>UF:</label>
<select name="uf" required>
<option value="">Selecione o Estado</option>
{% for uf in ['AC','AL','AP','AM','BA','CE','DF','ES','GO','MA','MT','MS','MG','PA','PB','PR','PE','PI','RJ','RN','RS','RO','RR','SC','SP','SE','TO'] %}
<option value="{{ uf }}">{{ uf }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>Exercício:</label>
<input name="exercicio" type="number" required />
</div>
<div class="form-group">
<label>Alíquota ICMS (%):</label>
<input name="aliquota" type="number" step="0.0001" required />
</div>
</div>
<!-- Bloco com espaçamento e alinhamento central -->
<div class="grupo-botoes">
<!-- Botão salvar -->
<button class="btn btn-primary" type="submit">💾 Salvar Alíquota</button>
<!-- Importação e template -->
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem;">
<label for="arquivo_aliquotas" class="btn btn-secondary" style="cursor: pointer;">
📎 Importar CSV
<input type="file" name="arquivo_aliquotas" accept=".csv" onchange="enviarArquivoAliquotas(this)" style="display: none;" />
</label>
<a href="/parametros/aliquotas/template" class="btn btn-secondary">📥 Baixar Template CSV</a>
</div>
</div>
</form>
<table class="selic-table">
<thead><tr><th>UF</th><th>Exercício</th><th>Alíquota</th></tr></thead>
<tbody id="tabela-aliquotas"></tbody>
</table>
</div>
</div>
<!-- ESTILOS -->
<style>
.tabs { display: flex; gap: 1rem; margin-bottom: 1rem; }
.tab { background: none; border: none; font-weight: bold; cursor: pointer; padding: 0.5rem 1rem; border-bottom: 2px solid transparent; }
.tab.active { border-bottom: 2px solid #2563eb; color: #2563eb; }
.tab-content { display: none; }
.tab-content.active { display: block; }
/* Abas */
.tabs {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 1rem;
}
.tab {
background: none;
border: none;
font-weight: bold;
cursor: pointer;
padding: 0.5rem 1rem;
border-bottom: 2px solid transparent;
}
.tab.active {
border-bottom: 2px solid #2563eb;
color: #2563eb;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* Formulário principal */
.formulario-box {
background: #fff;
padding: 2rem;
@@ -112,95 +207,473 @@
margin: 0 auto;
}
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; font-weight: bold; margin-bottom: 0.5rem; }
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 0.5rem;
}
.form-group input[type="text"],
.form-group input[type="number"],
.form-group input[type="date"],
.form-group textarea { width: 100%; padding: 0.6rem; border-radius: 6px; border: 1px solid #ccc; }
.form-group textarea {
width: 100%;
padding: 0.6rem;
border-radius: 6px;
border: 1px solid #ccc;
}
.form-group.check-group { display: flex; gap: 1rem; flex-wrap: wrap; }
.check-group label { display: flex; align-items: center; gap: 0.5rem; }
.form-group.check-group {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.check-group label {
display: flex;
align-items: center;
gap: 0.5rem;
}
.editor-box select { margin-bottom: 0.5rem; }
.editor-box textarea { font-family: monospace; }
.actions-inline { display: flex; gap: 1rem; align-items: center; margin-top: 0.5rem; }
/* Grade do formulário */
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; }
.btn-primary { background-color: #2563eb; color: white; }
.btn-secondary { background-color: #6c757d; color: white; }
.btn-danger { background-color: #dc3545; color: white; }
.btn-sm { font-size: 0.85rem; padding: 0.3rem 0.6rem; }
/* Botões */
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
}
.btn-primary {
background-color: #2563eb;
color: white;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-sm {
font-size: 0.85rem;
padding: 0.3rem 0.6rem;
}
/* Cards de fórmulas salvas */
.card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
align-items: start;
}
.param-card {
display: flex;
flex-direction: column;
justify-content: flex-start;
background: #f9f9f9;
padding: 1rem;
border-radius: 8px;
border-left: 4px solid #2563eb;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
box-sizing: border-box;
min-height: 200px;
}
.param-card.ativo {
border-left-color: #198754;
background: #f6fff9;
}
.param-card.inativo {
border-left-color: #adb5bd;
background: #f9f9f9;
}
.param-card input.edit-nome {
font-weight: bold;
font-size: 1rem;
border: none;
background: transparent;
outline: none;
flex: 1;
width: 100%;
padding: 0.25rem 0;
}
.edit-formula {
width: 100%;
font-family: monospace;
border: 1px solid #ccc;
border-radius: 6px;
padding: 0.5rem;
margin: 0.5rem 0;
resize: vertical;
min-height: 60px;
}
.param-card .actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 0.5rem;
}
.badge-status {
font-size: 0.75rem;
font-weight: bold;
padding: 0.2rem 0.6rem;
border-radius: 12px;
color: white;
background: #198754;
}
.param-card.inativo .badge-status {
background: #adb5bd;
}
/* Badge de campos e operadores */
.campo-badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.5rem 0;
}
.badge-campo, .badge-operador {
background: #e0e7ff;
color: #1e3a8a;
padding: 0.3rem 0.6rem;
border-radius: 8px;
cursor: pointer;
font-family: monospace;
transition: background 0.2s ease;
}
.badge-campo:hover, .badge-operador:hover {
background: #c7d2fe;
}
/* Mensagens */
.mensagem-info {
background: #e0f7fa;
padding: 1rem;
border-left: 4px solid #2563eb;
border-radius: 6px;
color: #007b83;
font-size: 0.85rem;
}
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
/* Tabela SELIC */
.selic-table {
width: 100%;
margin-top: 1rem;
border-collapse: collapse;
}
.param-card {
background: #f9f9f9;
padding: 1rem;
border-radius: 8px;
border-left: 4px solid #2563eb;
.selic-table th,
.selic-table td {
padding: 0.6rem;
border-bottom: 1px solid #ccc;
}
.selic-table th {
text-align: left;
background: #f1f1f1;
}
.param-card code { display: block; margin: 0.5rem 0; color: #333; }
.actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.selic-table { width: 100%; margin-top: 1rem; border-collapse: collapse; }
.selic-table th, .selic-table td { padding: 0.6rem; border-bottom: 1px solid #ccc; }
.selic-table th { text-align: left; background: #f1f1f1; }
/* Popup de feedback */
.feedback-popup {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 99999;
}
.feedback-content {
background-color: #fff;
padding: 2rem;
border-radius: 12px;
text-align: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
}
.feedback-content h3 {
margin-bottom: 1rem;
}
.feedback-content p {
margin: 0.25rem 0;
font-weight: 500;
}
.hidden {
display: none !important;
}
.form-group select {
width: 100%;
padding: 0.6rem;
border-radius: 6px;
border: 1px solid #ccc;
font-family: inherit;
}
.btn {
text-decoration: none;
}
.grupo-botoes {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 1.5rem;
gap: 1rem;
}
.btn {
padding: 0.5rem 1rem;
font-weight: 600;
font-size: 1rem;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
text-decoration: none !important;
}
</style>
{% block scripts %}
<script>
// 🟡 Alterna entre abas
function switchTab(tabId) {
// Remove classe 'active' de todos os botões e abas
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
// Ativa a aba correspondente
document.getElementById(tabId).classList.add('active');
event.target.classList.add('active');
// Ativa o botão correspondente
const button = document.querySelector(`.tab[onclick="switchTab('${tabId}')"]`);
if (button) button.classList.add('active');
}
function inserirCampo(select) {
const campo = select.value;
if (campo) {
const formula = document.getElementById("formula");
const start = formula.selectionStart;
const end = formula.selectionEnd;
formula.value = formula.value.slice(0, start) + campo + formula.value.slice(end);
formula.focus();
formula.setSelectionRange(start + campo.length, start + campo.length);
select.selectedIndex = 0;
}
// ✅ Insere valores no editor de fórmulas
function inserirNoEditor(valor) {
const formula = document.getElementById("formula");
const start = formula.selectionStart;
const end = formula.selectionEnd;
formula.value = formula.value.slice(0, start) + valor + formula.value.slice(end);
formula.focus();
formula.setSelectionRange(start + valor.length, start + valor.length);
}
// ✅ Feedback visual em popup
function mostrarFeedback(titulo, mensagem) {
document.getElementById("feedback-titulo").innerText = titulo;
document.getElementById("feedback-mensagem").innerText = mensagem;
document.getElementById("parametros-feedback").classList.remove("hidden");
}
function fecharFeedbackParametros() {
document.getElementById("parametros-feedback").classList.add("hidden");
}
// ✅ Testa fórmula principal (formulário superior)
function testarFormula() {
const formula = document.getElementById("formula").value;
const vars = {
pis_base: 84.38,
cofins_base: 84.38,
icms_valor: 24.47,
pis_aliq: 0.012872,
cofins_aliq: 0.059287
};
try {
const resultado = eval(formula.replace(/\b(\w+)\b/g, match => vars.hasOwnProperty(match) ? vars[match] : match));
document.getElementById("resultado-teste").innerText = "Resultado: R$ " + resultado.toFixed(5);
document.getElementById("resultado-teste").style.display = "block";
} catch (e) {
document.getElementById("resultado-teste").innerText = "Erro: " + e.message;
document.getElementById("resultado-teste").style.display = "block";
const nome = document.getElementById("nome").value.trim();
const formula = document.getElementById("formula").value.trim();
const output = document.getElementById("resultado-teste");
if (!nome || !formula) {
output.innerText = "❌ Preencha o nome e a fórmula para testar.";
output.style.display = "block";
return;
}
fetch("/parametros/testar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ nome, formula })
})
.then(res => res.json())
.then(data => {
output.style.display = "block";
if (data.success) {
output.innerText = `✅ Fórmula válida. Resultado: ${data.resultado}`;
} else {
output.innerText = `❌ Erro: ${data.error}`;
}
});
}
// ✅ Testa fórmula inline nos cards salvos
function testarFormulaInline(id) {
const nome = document.querySelector(`.edit-nome[data-id='${id}']`)?.value;
const formula = document.querySelector(`.edit-formula[data-id='${id}']`)?.value;
const output = document.getElementById(`resultado-inline-${id}`);
if (!formula) {
output.innerText = "❌ Fórmula não preenchida.";
output.style.display = 'block';
return;
}
fetch("/parametros/testar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ nome, formula })
})
.then(res => res.json())
.then(data => {
output.style.display = 'block';
if (data.success) {
output.innerText = `✅ Fórmula válida. Resultado: ${data.resultado}`;
} else {
output.innerText = `❌ Erro: ${data.error}`;
}
});
}
// ✅ Salva edição inline nos cards
async function salvarInline(id) {
const inputNome = document.querySelector(`.edit-nome[data-id='${id}']`);
const textareaFormula = document.querySelector(`.edit-formula[data-id='${id}']`);
const nome = inputNome.value.trim();
const formula = textareaFormula.value.trim();
if (!nome || !formula) {
alert("Preencha todos os campos.");
return;
}
const response = await fetch(`/parametros/editar/${id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nome, formula })
});
if (response.ok) {
mostrarFeedback("✅ Atualizado", "Parâmetro salvo com sucesso.");
} else {
mostrarFeedback("❌ Erro", "Erro ao salvar.");
}
}
// ✅ Carrega tabela de alíquotas
async function carregarAliquotas() {
const res = await fetch("/parametros/aliquotas");
const dados = await res.json();
const tbody = document.getElementById("tabela-aliquotas");
tbody.innerHTML = dados.map(a => `
<tr><td>${a.uf}</td><td>${a.exercicio}</td><td>${a.aliquota.toFixed(4)}%</td></tr>
`).join('');
}
// ✅ Eventos após carregar DOM
document.addEventListener('DOMContentLoaded', () => {
carregarAliquotas();
// Ativar/desativar checkbox
document.querySelectorAll('.toggle-ativo').forEach(input => {
input.addEventListener('change', async function () {
const id = this.dataset.id;
const ativo = this.checked;
const response = await fetch(`/parametros/ativar/${id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ativo })
});
if (response.ok) {
location.reload();
} else {
alert('Erro ao atualizar status.');
}
});
});
// Botões de teste inline
document.querySelectorAll('.btn-testar').forEach(button => {
button.addEventListener('click', function () {
const id = this.dataset.id;
testarFormulaInline(id);
});
});
});
function mostrarLoadingSelic() {
document.getElementById("selic-loading").classList.remove("hidden");
}
window.addEventListener("DOMContentLoaded", () => {
const abaUrl = new URLSearchParams(window.location.search).get("aba");
if (abaUrl === "selic") {
document.querySelector(".tab.active")?.classList.remove("active");
document.querySelector(".tab-content.active")?.classList.remove("active");
document.querySelector(".tab:nth-child(2)").classList.add("active"); // Ativa o botão da aba
document.getElementById("selic").classList.add("active"); // Ativa o conteúdo da aba
}
});
function enviarArquivoAliquotas(input) {
const file = input.files[0];
if (!file) return;
const formData = new FormData();
formData.append("arquivo", file);
fetch("/parametros/aliquotas/importar", {
method: "POST",
body: formData
})
.then(res => res.json())
.then(data => {
if (data.success) {
mostrarFeedback("✅ Importado", `${data.qtd} alíquotas foram importadas.`);
carregarAliquotas();
} else {
mostrarFeedback("❌ Erro", data.error || "Falha na importação.");
}
});
}
</script>
<!-- Feedback estilo popup -->
<div id="parametros-feedback" class="feedback-popup hidden">
<div class="feedback-content">
<h3 id="feedback-titulo">✅ Ação Concluída</h3>
<p id="feedback-mensagem">Parâmetro salvo com sucesso.</p>
<button onclick="fecharFeedbackParametros()" class="btn btn-primary" style="margin-top: 1rem;">OK</button>
</div>
</div>
<div id="selic-loading" class="feedback-popup hidden">
<div class="feedback-content">
<h3>⏳ Atualizando SELIC</h3>
<p>Aguarde enquanto os fatores SELIC estão sendo carregados...</p>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -16,6 +16,12 @@
<div class="buttons">
<button class="btn btn-primary" onclick="processar(this)">Processar Faturas</button>
<button class="btn btn-primary pulse" onclick="limpar()" style="font-weight: bold;">🔁 Novo Processo</button>
{% if status_resultados|selectattr("status", "equalto", "Erro")|list %}
<div style="margin-top: 2rem;">
<a class="btn btn-danger" href="/erros/download">⬇️ Baixar Faturas com Erro (.zip)</a>
<a class="btn btn-secondary" href="/erros/log">📄 Ver Log de Erros (.txt)</a>
</div>
{% endif %}
<button class="btn btn-success" onclick="baixarPlanilha()">📅 Abrir Planilha</button>
<button class="btn btn-success" onclick="gerarRelatorio()">📊 Gerar Relatório</button>
{% if app_env != "producao" %}
@@ -28,7 +34,7 @@
</div>
<div id="tabela-wrapper" class="tabela-wrapper"></div>
</div>
ar
<script>
let arquivos = [];
let statusInterval = null;
@@ -77,7 +83,10 @@ function renderTable(statusList = []) {
grupo === 'Duplicado' ? '📄' :
'⌛'} ${file.status}
</td>
<td>${file.tempo || '---'}</td>
<td>
${file.mensagem ? `<div class="status-msg">${file.mensagem}</div>` : ""}
${file.tempo || '---'}
</td>
</tr>
`).join('');
@@ -517,6 +526,12 @@ function fecharFeedback() {
100% { transform: scale(1); }
}
.status-msg {
color: #dc3545;
font-size: 0.8rem;
margin-top: 0.25rem;
white-space: pre-wrap;
}
</style>