|
|
<!DOCTYPE html> |
|
|
<html lang="pt-br"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Dashboard Notas Fiscais AI (Static)</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> |
|
|
<script> |
|
|
|
|
|
|
|
|
const API_URL = "http://localhost:8000"; |
|
|
</script> |
|
|
</head> |
|
|
<body class="bg-gray-100 font-sans"> |
|
|
|
|
|
<div class="flex h-screen overflow-hidden"> |
|
|
|
|
|
<div class="bg-gray-800 text-white w-64 flex-shrink-0"> |
|
|
<div class="p-4 text-2xl font-bold border-b border-gray-700"> |
|
|
<i class="fas fa-file-invoice-dollar mr-2"></i> ERP AI |
|
|
</div> |
|
|
<nav class="mt-4"> |
|
|
<a href="#" class="block py-2.5 px-4 bg-gray-700 border-l-4 border-blue-500"> |
|
|
<i class="fas fa-tachometer-alt mr-2"></i> Dashboard |
|
|
</a> |
|
|
</nav> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex-1 flex flex-col overflow-hidden"> |
|
|
|
|
|
<header class="bg-white shadow p-4 flex justify-between items-center"> |
|
|
<h2 class="text-xl font-semibold text-gray-800">Visão Geral</h2> |
|
|
<button class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition" onclick="document.getElementById('uploadModal').classList.remove('hidden')"> |
|
|
<i class="fas fa-plus mr-2"></i> Nova Nota Fiscal |
|
|
</button> |
|
|
</header> |
|
|
|
|
|
|
|
|
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-100 p-6"> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6"> |
|
|
<div class="bg-white p-6 rounded-lg shadow border-l-4 border-green-500"> |
|
|
<div class="flex items-center"> |
|
|
<div class="p-3 rounded-full bg-green-100 text-green-500 mr-4"> |
|
|
<i class="fas fa-dollar-sign fa-2x"></i> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-gray-500 text-sm">Total Processado</p> |
|
|
<p id="totalRevenue" class="text-2xl font-bold text-gray-800">R$ 0,00</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white p-6 rounded-lg shadow border-l-4 border-blue-500"> |
|
|
<div class="flex items-center"> |
|
|
<div class="p-3 rounded-full bg-blue-100 text-blue-500 mr-4"> |
|
|
<i class="fas fa-file-alt fa-2x"></i> |
|
|
</div> |
|
|
<div> |
|
|
<p class="text-gray-500 text-sm">Notas Cadastradas</p> |
|
|
<p id="totalInvoices" class="text-2xl font-bold text-gray-800">0</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white shadow rounded-lg overflow-hidden"> |
|
|
<div class="px-6 py-4 border-b border-gray-200"> |
|
|
<h3 class="text-lg font-semibold text-gray-800">Últimas Notas Fiscais</h3> |
|
|
</div> |
|
|
<table class="min-w-full leading-normal"> |
|
|
<thead> |
|
|
<tr> |
|
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">ID</th> |
|
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Emissor</th> |
|
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Data</th> |
|
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Valor</th> |
|
|
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Status</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody id="invoicesTableBody"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</main> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="uploadModal" class="fixed z-10 inset-0 overflow-y-auto hidden" aria-labelledby="modal-title" role="dialog" aria-modal="true"> |
|
|
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> |
|
|
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true" onclick="document.getElementById('uploadModal').classList.add('hidden')"></div> |
|
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"> |
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> |
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">Upload de Nota Fiscal</h3> |
|
|
<div class="mt-2"> |
|
|
<input type="file" id="fileInput" class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"/> |
|
|
<div id="uploadStatus" class="mt-2 text-sm font-medium hidden"></div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> |
|
|
<button type="button" onclick="uploadFile()" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 sm:ml-3 sm:w-auto sm:text-sm">Processar</button> |
|
|
<button type="button" onclick="document.getElementById('uploadModal').classList.add('hidden')" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">Cancelar</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
async function fetchInvoices() { |
|
|
try { |
|
|
const response = await fetch(`${API_URL}/invoices`); |
|
|
const invoices = await response.json(); |
|
|
|
|
|
const tbody = document.getElementById('invoicesTableBody'); |
|
|
tbody.innerHTML = ''; |
|
|
|
|
|
let totalVal = 0; |
|
|
|
|
|
|
|
|
invoices.sort((a, b) => b.id - a.id); |
|
|
|
|
|
invoices.forEach(inv => { |
|
|
if (inv.total_value) totalVal += inv.total_value; |
|
|
|
|
|
const date = new Date(inv.created_at).toLocaleString('pt-BR'); |
|
|
const value = inv.total_value ? `R$ ${inv.total_value.toFixed(2)}` : 'R$ 0.00'; |
|
|
|
|
|
const statusColor = inv.status === 'processed' ? 'text-green-900 bg-green-200' : |
|
|
(inv.status === 'failed' ? 'text-red-900 bg-red-200' : 'text-yellow-900 bg-yellow-200'); |
|
|
|
|
|
const row = ` |
|
|
<tr> |
|
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm"><p class="text-gray-900 whitespace-no-wrap">#${inv.id}</p></td> |
|
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm"> |
|
|
<p class="text-gray-900 whitespace-no-wrap font-medium">${inv.issuer || 'Desconhecido'}</p> |
|
|
<p class="text-gray-500 text-xs">${inv.filename}</p> |
|
|
</td> |
|
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm"><p class="text-gray-900 whitespace-no-wrap">${date}</p></td> |
|
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm"><p class="text-gray-900 whitespace-no-wrap font-bold">${value}</p></td> |
|
|
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm"> |
|
|
<span class="relative inline-block px-3 py-1 font-semibold leading-tight ${statusColor.split(' ')[0]}"> |
|
|
<span aria-hidden class="absolute inset-0 opacity-50 rounded-full ${statusColor.split(' ')[1]}"></span> |
|
|
<span class="relative">${inv.status}</span> |
|
|
</span> |
|
|
</td> |
|
|
</tr> |
|
|
`; |
|
|
tbody.innerHTML += row; |
|
|
}); |
|
|
|
|
|
document.getElementById('totalRevenue').textContent = `R$ ${totalVal.toFixed(2)}`; |
|
|
document.getElementById('totalInvoices').textContent = invoices.length; |
|
|
|
|
|
} catch (error) { |
|
|
console.error("Erro ao buscar notas:", error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function uploadFile() { |
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const statusDiv = document.getElementById('uploadStatus'); |
|
|
|
|
|
if (!fileInput.files[0]) { |
|
|
alert('Selecione um arquivo!'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const formData = new FormData(); |
|
|
formData.append('file', fileInput.files[0]); |
|
|
|
|
|
statusDiv.textContent = "Processando..."; |
|
|
statusDiv.classList.remove('hidden', 'text-green-600', 'text-red-600'); |
|
|
statusDiv.classList.add('text-blue-600'); |
|
|
|
|
|
try { |
|
|
const response = await fetch(`${API_URL}/upload`, { |
|
|
method: 'POST', |
|
|
body: formData |
|
|
}); |
|
|
|
|
|
if (response.ok) { |
|
|
statusDiv.textContent = "Sucesso!"; |
|
|
statusDiv.classList.replace('text-blue-600', 'text-green-600'); |
|
|
fetchInvoices(); |
|
|
setTimeout(() => { |
|
|
document.getElementById('uploadModal').classList.add('hidden'); |
|
|
statusDiv.classList.add('hidden'); |
|
|
fileInput.value = ''; |
|
|
}, 1500); |
|
|
} else { |
|
|
throw new Error('Upload failed'); |
|
|
} |
|
|
} catch (error) { |
|
|
statusDiv.textContent = "Erro!"; |
|
|
statusDiv.classList.replace('text-blue-600', 'text-red-600'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fetchInvoices(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|