feat: primeira versão da produção
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
107
app/templates/dashboard.html
Executable file
107
app/templates/dashboard.html
Executable file
@@ -0,0 +1,107 @@
|
||||
{% extends "index.html" %}
|
||||
{% block title %}Dashboard{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1 style="display: flex; align-items: center; gap: 10px;">
|
||||
<i class="fas fa-chart-line"></i> Dashboard de Faturas
|
||||
</h1>
|
||||
|
||||
<form method="get" style="margin: 20px 0;">
|
||||
<label for="cliente">Selecionar Cliente:</label>
|
||||
<select name="cliente" id="cliente" onchange="this.form.submit()">
|
||||
<option value="">Todos</option>
|
||||
{% for c in clientes %}
|
||||
<option value="{{ c }}" {% if c == cliente_atual %}selected{% endif %}>{{ c }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<!-- Cards -->
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 30px;">
|
||||
{% for indicador in indicadores %}
|
||||
<div style="
|
||||
flex: 1 1 220px;
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
|
||||
">
|
||||
<strong>{{ indicador.titulo }}</strong>
|
||||
<div style="font-size: 1.6rem; font-weight: bold; margin-top: 10px;">
|
||||
{{ indicador.valor }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<h2 style="margin-bottom: 20px;"><i class="fas fa-chart-bar"></i> Análise da Decisão do STF (RE 574.706 – 15/03/2017)</h2>
|
||||
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 20px;">
|
||||
<div style="flex: 1;">
|
||||
<h4>% de Faturas com ICMS na Base PIS/COFINS</h4>
|
||||
<canvas id="graficoICMS"></canvas>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<h4>Valor Médio de Tributos com ICMS</h4>
|
||||
<canvas id="graficoValor"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
const ctx1 = document.getElementById('graficoICMS').getContext('2d');
|
||||
new Chart(ctx1, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Antes da Decisão', 'Depois da Decisão'],
|
||||
datasets: [{
|
||||
label: '% com ICMS na Base',
|
||||
data: {{ [analise_stf.antes.percentual_com_icms, analise_stf.depois.percentual_com_icms] | tojson }},
|
||||
backgroundColor: ['#f39c12', '#e74c3c']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
title: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: { display: true, text: '%' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const ctx2 = document.getElementById('graficoValor').getContext('2d');
|
||||
new Chart(ctx2, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Antes da Decisão', 'Depois da Decisão'],
|
||||
datasets: [{
|
||||
label: 'Valor Médio de PIS/COFINS com ICMS',
|
||||
data: {{ [analise_stf.antes.media_valor, analise_stf.depois.media_valor] | tojson }},
|
||||
backgroundColor: ['#2980b9', '#27ae60']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
title: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: { display: true, text: 'R$' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
184
app/templates/index.html
Normal file
184
app/templates/index.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>{% block title %}ProcessaWatt{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⚡️</text></svg>">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary: #2563eb;
|
||||
--primary-dark: #1e40af;
|
||||
--sidebar-width: 250px;
|
||||
--sidebar-collapsed: 80px;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background: #f8fafc;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: var(--sidebar-collapsed);
|
||||
height: 100vh;
|
||||
background: linear-gradient(to bottom, var(--primary), var(--primary-dark));
|
||||
position: fixed;
|
||||
transition: all 0.3s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar.expanded {
|
||||
width: var(--sidebar-width);
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
gap: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--primary);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar.expanded .app-name {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.menu {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
color: rgba(255,255,255,0.9);
|
||||
text-decoration: none;
|
||||
padding-left: 20px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.menu-item i {
|
||||
font-size: 1.2rem;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.menu-item span {
|
||||
margin-left: 15px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.sidebar.expanded .menu-item span {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: var(--sidebar-collapsed);
|
||||
padding: 30px;
|
||||
flex-grow: 1;
|
||||
transition: margin-left 0.3s;
|
||||
}
|
||||
|
||||
.sidebar.expanded ~ .main-content {
|
||||
margin-left: var(--sidebar-width);
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
padding: 10px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 30px 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar" id="sidebar">
|
||||
<div class="logo-container" onclick="toggleSidebar()">
|
||||
<div class="logo-icon">⚡️</div>
|
||||
<div class="app-name">ProcessaWatt</div>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<a href="/" class="menu-item">
|
||||
<i class="fas fa-tachometer-alt"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="/upload" class="menu-item">
|
||||
<i class="fas fa-upload"></i>
|
||||
<span>Upload</span>
|
||||
</a>
|
||||
<a href="/relatorios" class="menu-item">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
<span>Relatórios</span>
|
||||
</a>
|
||||
<a href="/parametros" class="menu-item">
|
||||
<i class="fas fa-cog"></i>
|
||||
<span>Parâmetros</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
document.getElementById('sidebar').classList.toggle('expanded');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
201
app/templates/index2.html
Executable file
201
app/templates/index2.html
Executable file
@@ -0,0 +1,201 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Processador de Faturas</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Montserrat', sans-serif; }
|
||||
body { background-color: #f7f9fc; padding: 2rem; color: #333; }
|
||||
|
||||
.nav { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
|
||||
.nav h1 { font-size: 1.5rem; color: #4361ee; }
|
||||
.nav ul { display: flex; list-style: none; gap: 1.5rem; }
|
||||
.nav li a { text-decoration: none; color: #333; font-weight: 600; }
|
||||
.nav li a:hover { color: #4361ee; }
|
||||
|
||||
.upload-box {
|
||||
background: #fff;
|
||||
border: 2px dashed #ccc;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.upload-box.dragover {
|
||||
background-color: #eef2ff;
|
||||
border-color: #4361ee;
|
||||
}
|
||||
|
||||
.buttons { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; margin-bottom: 2rem; }
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
.btn:hover { opacity: 0.9; }
|
||||
.btn-primary { background-color: #4361ee; color: white; }
|
||||
.btn-success { background-color: #198754; color: white; }
|
||||
.btn-danger { background-color: #dc3545; color: white; }
|
||||
.btn-secondary { background-color: #6c757d; color: white; }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
th, td {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
font-size: 0.85rem;
|
||||
color: #555;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#file-input { display: none; }
|
||||
|
||||
.status-ok { color: #198754; }
|
||||
.status-error { color: #dc3545; }
|
||||
.status-warn { color: #ffc107; }
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 3rem;
|
||||
font-size: 0.8rem;
|
||||
color: #aaa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav">
|
||||
<h1>📄 Processador de Faturas</h1>
|
||||
<ul>
|
||||
<li><a href="/">Upload</a></li>
|
||||
<li><a href="/dashboard">Dashboard</a></li>
|
||||
<li><a href="/relatorios">Relatórios</a></li>
|
||||
<li><a href="/parametros">Parâmetros</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<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>
|
||||
<br />
|
||||
<button class="btn btn-primary" onclick="document.getElementById('file-input').click()">Selecionar Arquivos</button>
|
||||
<input type="file" id="file-input" accept=".pdf" multiple onchange="handleFiles(this.files)" />
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button class="btn btn-primary" onclick="processar()">Processar Faturas</button>
|
||||
<button class="btn btn-danger" onclick="limpar()">Limpar Tudo</button>
|
||||
<button class="btn btn-success" onclick="baixarPlanilha()">📥 Abrir Planilha</button>
|
||||
<button class="btn btn-success" onclick="gerarRelatorio()">📊 Gerar Relatório</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arquivo</th>
|
||||
<th>Status</th>
|
||||
<th>Mensagem</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="file-table">
|
||||
<tr><td colspan="3" style="text-align: center; color: #aaa;">Nenhum arquivo selecionado.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<footer>
|
||||
Sistema desenvolvido para análise tributária de faturas (PIS/COFINS/ICMS) com correção SELIC.
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
let arquivos = [];
|
||||
let statusInterval = null;
|
||||
const fileTable = document.getElementById('file-table');
|
||||
|
||||
function handleFiles(files) {
|
||||
arquivos = [...arquivos, ...files];
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function renderTable(statusList = []) {
|
||||
const rows = statusList.length ? statusList : arquivos.map(file => ({ nome: file.name, status: 'Aguardando', mensagem: '' }));
|
||||
fileTable.innerHTML = rows.length
|
||||
? rows.map(file => `
|
||||
<tr>
|
||||
<td>${file.nome}</td>
|
||||
<td class="${file.status === 'Concluido' ? 'status-ok' : file.status === 'Erro' ? 'status-error' : 'status-warn'}">${file.status}</td>
|
||||
<td>${file.mensagem || '---'}</td>
|
||||
</tr>
|
||||
`).join('')
|
||||
: '<tr><td colspan="3" style="text-align:center; color: #aaa;">Nenhum arquivo selecionado.</td></tr>';
|
||||
}
|
||||
|
||||
async function processar() {
|
||||
if (arquivos.length === 0) return alert("Nenhum arquivo selecionado.");
|
||||
const formData = new FormData();
|
||||
arquivos.forEach(file => formData.append("files", file));
|
||||
|
||||
await fetch("/upload-files", { method: "POST", body: formData });
|
||||
await fetch("/process-queue", { method: "POST" });
|
||||
arquivos = [];
|
||||
statusInterval = setInterval(updateStatus, 1000);
|
||||
}
|
||||
|
||||
async function updateStatus() {
|
||||
const res = await fetch("/get-status");
|
||||
const data = await res.json();
|
||||
renderTable(data.files);
|
||||
|
||||
if (!data.is_processing && statusInterval) {
|
||||
clearInterval(statusInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function limpar() {
|
||||
fetch("/clear-all", { method: "POST" });
|
||||
arquivos = [];
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function baixarPlanilha() {
|
||||
window.open('/download-spreadsheet', '_blank');
|
||||
}
|
||||
|
||||
function gerarRelatorio() {
|
||||
window.open('/generate-report', '_blank');
|
||||
}
|
||||
|
||||
const dropZone = document.getElementById('upload-box');
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('dragover');
|
||||
});
|
||||
dropZone.addEventListener('dragleave', () => {
|
||||
dropZone.classList.remove('dragover');
|
||||
});
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('dragover');
|
||||
handleFiles(e.dataTransfer.files);
|
||||
});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', updateStatus);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
32
app/templates/parametros.html
Executable file
32
app/templates/parametros.html
Executable file
@@ -0,0 +1,32 @@
|
||||
{% extends "index.html" %}
|
||||
{% block title %}Parâmetros de Cálculo{% endblock %}
|
||||
{% block content %}
|
||||
<h1>⚙️ Parâmetros</h1>
|
||||
|
||||
<form method="post">
|
||||
<label for="tipo">Tipo:</label><br>
|
||||
<input type="text" name="tipo" id="tipo" value="{{ parametros.tipo or '' }}" required/><br><br>
|
||||
|
||||
<label for="formula">Fórmula:</label><br>
|
||||
<input type="text" name="formula" id="formula" value="{{ parametros.formula or '' }}" required/><br><br>
|
||||
|
||||
<label for="aliquota_icms">Alíquota de ICMS (%):</label><br>
|
||||
<input type="number" step="0.01" name="aliquota_icms" id="aliquota_icms" value="{{ parametros.aliquota_icms or '' }}" /><br><br>
|
||||
|
||||
<label for="incluir_icms">Incluir ICMS:</label><br>
|
||||
<input type="checkbox" name="incluir_icms" id="incluir_icms" value="1" {% if parametros.incluir_icms %}checked{% endif %}><br><br>
|
||||
|
||||
<label for="ativo">Ativo:</label><br>
|
||||
<input type="checkbox" name="ativo" id="ativo" value="1" {% if parametros.ativo %}checked{% endif %}><br><br>
|
||||
|
||||
<button type="submit" style="padding: 10px 20px; background: #2563eb; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
Salvar Parâmetros
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if mensagem %}
|
||||
<div style="margin-top: 20px; background: #e0f7fa; padding: 10px; border-left: 4px solid #2563eb;">
|
||||
{{ mensagem }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
38
app/templates/relatorios.html
Executable file
38
app/templates/relatorios.html
Executable file
@@ -0,0 +1,38 @@
|
||||
{% extends "index.html" %}
|
||||
{% block title %}Relatórios{% endblock %}
|
||||
{% block content %}
|
||||
<h1>📊 Relatórios</h1>
|
||||
|
||||
<form method="get" style="margin-bottom: 20px;">
|
||||
<label for="cliente">Filtrar por Cliente:</label>
|
||||
<select name="cliente" id="cliente" onchange="this.form.submit()">
|
||||
<option value="">Todos</option>
|
||||
{% for c in clientes %}
|
||||
<option value="{{ c }}" {% if c == cliente_atual %}selected{% endif %}>{{ c }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr style="background: #2563eb; color: white;">
|
||||
<th style="padding: 10px;">Cliente</th>
|
||||
<th>Data</th>
|
||||
<th>Valor Total</th>
|
||||
<th>ICMS na Base</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for f in faturas %}
|
||||
<tr style="background: {{ loop.cycle('#ffffff', '#f0f4f8') }};">
|
||||
<td style="padding: 10px;">{{ f.nome }}</td>
|
||||
<td>{{ f.data_emissao }}</td>
|
||||
<td>R$ {{ '%.2f'|format(f.valor_total)|replace('.', ',') }}</td>
|
||||
<td>{{ 'Sim' if f.com_icms else 'Não' }}</td>
|
||||
<td>{{ f.status }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
184
app/templates/upload.html
Executable file
184
app/templates/upload.html
Executable file
@@ -0,0 +1,184 @@
|
||||
{% extends "index.html" %}
|
||||
{% block title %}Upload de Faturas{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1 style="font-size: 1.5rem; margin-bottom: 1rem;">📤 Upload de Faturas</h1>
|
||||
|
||||
<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>
|
||||
<br />
|
||||
<button class="btn btn-primary" onclick="document.getElementById('file-input').click()">Selecionar Arquivos</button>
|
||||
<input type="file" id="file-input" accept=".pdf" multiple onchange="handleFiles(this.files)" style="display:none;" />
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button class="btn btn-primary" onclick="processar(this)">Processar Faturas</button>
|
||||
<button class="btn btn-danger" onclick="limpar()">Limpar Tudo</button>
|
||||
<button class="btn btn-success" onclick="baixarPlanilha()">📅 Abrir Planilha</button>
|
||||
<button class="btn btn-success" onclick="gerarRelatorio()">📊 Gerar Relatório</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arquivo</th>
|
||||
<th>Status</th>
|
||||
<th>Mensagem</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="file-table">
|
||||
<tr><td colspan="3" style="text-align: center; color: #aaa;">Nenhum arquivo selecionado.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
let arquivos = [];
|
||||
let statusInterval = null;
|
||||
const fileTable = document.getElementById('file-table');
|
||||
|
||||
function handleFiles(files) {
|
||||
arquivos = [...arquivos, ...files];
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function renderTable(statusList = []) {
|
||||
const rows = statusList.length ? statusList : arquivos.map(file => ({ nome: file.name, status: 'Aguardando', mensagem: '' }));
|
||||
fileTable.innerHTML = rows.length
|
||||
? rows.map(file => `
|
||||
<tr>
|
||||
<td>${file.nome}</td>
|
||||
<td class="${file.status === 'Concluido' ? 'status-ok' : file.status === 'Erro' ? 'status-error' : 'status-warn'}">${file.status}</td>
|
||||
<td>${file.mensagem || '---'}</td>
|
||||
</tr>
|
||||
`).join('')
|
||||
: '<tr><td colspan="3" style="text-align:center; color: #aaa;">Nenhum arquivo selecionado.</td></tr>';
|
||||
}
|
||||
|
||||
async function processar(btn) {
|
||||
if (arquivos.length === 0) return alert("Nenhum arquivo selecionado.");
|
||||
btn.disabled = true;
|
||||
btn.innerText = "⏳ Processando...";
|
||||
|
||||
const formData = new FormData();
|
||||
arquivos.forEach(file => formData.append("files", file));
|
||||
|
||||
try {
|
||||
await fetch("/upload-files", { method: "POST", body: formData });
|
||||
await fetch("/process-queue", { method: "POST" });
|
||||
arquivos = [];
|
||||
statusInterval = setInterval(updateStatus, 1000);
|
||||
} catch (err) {
|
||||
alert("Erro ao processar faturas.");
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
btn.innerText = "Processar Faturas";
|
||||
btn.disabled = false;
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateStatus() {
|
||||
const res = await fetch("/get-status");
|
||||
const data = await res.json();
|
||||
renderTable(data.files);
|
||||
|
||||
if (!data.is_processing && statusInterval) {
|
||||
clearInterval(statusInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function limpar() {
|
||||
fetch("/clear-all", { method: "POST" });
|
||||
arquivos = [];
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function baixarPlanilha() {
|
||||
window.open('/export-excel', '_blank');
|
||||
}
|
||||
|
||||
function gerarRelatorio() {
|
||||
window.open('/generate-report', '_blank');
|
||||
}
|
||||
|
||||
const dropZone = document.getElementById('upload-box');
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('dragover');
|
||||
});
|
||||
dropZone.addEventListener('dragleave', () => {
|
||||
dropZone.classList.remove('dragover');
|
||||
});
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('dragover');
|
||||
handleFiles(e.dataTransfer.files);
|
||||
});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', updateStatus);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.upload-box {
|
||||
background: #fff;
|
||||
border: 2px dashed #ccc;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.upload-box.dragover {
|
||||
background-color: #eef2ff;
|
||||
border-color: #4361ee;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
.btn:hover { opacity: 0.9; }
|
||||
.btn-primary { background-color: #4361ee; color: white; }
|
||||
.btn-success { background-color: #198754; color: white; }
|
||||
.btn-danger { background-color: #dc3545; color: white; }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
font-size: 0.85rem;
|
||||
color: #555;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.status-ok { color: #198754; }
|
||||
.status-error { color: #dc3545; }
|
||||
.status-warn { color: #ffc107; }
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user