PDF link target=_blank + nginx timeouts + priority filteri (samo s podacima)
nginx (sport.rinet.one): - proxy_read_timeout 60s → 300s - proxy_send_timeout 300s - proxy_buffering off (PDF stream) - client_max_body_size 50M → 100M Endpoints: - /api/v2/klubovi/financirani: +with_data filter (samo s potporama/godišnjakom/HNS) - /api/v2/sportasi/filtered: +samo_priority +samo_s_hns Frontend: - PDF link target=_blank rel=noopener - window._klub_only_priority = true (default) - window._sportas_only_priority = true (default) DB View: - pgz_sport.v_nogomet_priority (prima_potpore, u_godisnjaku, ima_hns_roster)
This commit is contained in:
+79
-5
@@ -222,14 +222,24 @@ table tbody tr:hover{background:var(--bg3)}
|
||||
<!-- ============ INVOICE UPLOADS (OCR) ============ -->
|
||||
<section class="panel" id="panel-uploads">
|
||||
<div class="card">
|
||||
<div class="card-h"><div class="card-t">Invoice Uploads (OCR/AI extraction)</div></div>
|
||||
<div class="card-h">
|
||||
<div class="card-t">Računi (OCR) — Invoice Uploads / AI extraction</div>
|
||||
<div style="display:flex;gap:6px">
|
||||
<button class="btn primary" onclick="document.getElementById('up-file').click()">📎 Upload novi račun</button>
|
||||
<input type="file" id="up-file" accept="application/pdf,image/*" style="display:none" onchange="uploadInvoiceFile(this.files[0])">
|
||||
</div>
|
||||
</div>
|
||||
<div id="up-drop" style="border:2px dashed var(--rim2);border-radius:8px;padding:18px;text-align:center;color:var(--t2);margin-bottom:10px;background:var(--bg3)">
|
||||
Dovuci PDF / JPG / PNG ovdje (max 25 MB) ili koristi gumb gore.
|
||||
<div id="up-progress" style="margin-top:6px;font-size:11px;color:var(--t1)"></div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<input id="up-q" placeholder="Datoteka / vendor / broj…">
|
||||
<label>Status <select id="up-status"><option value="">— svi —</option><option value="pending">pending</option><option value="ocr_done">ocr_done</option><option value="approved">approved</option><option value="rejected">rejected</option></select></label>
|
||||
<button class="btn" onclick="loadUploads()">Osvježi</button>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table id="up-tbl"><thead><tr><th>#</th><th>Datoteka</th><th class="num">Veličina</th><th>Vendor</th><th>OIB</th><th>Br. računa</th><th>Datum</th><th class="num">Brutto</th><th>OCR status</th><th class="num">Conf</th><th>Račun</th></tr></thead><tbody><tr><td colspan="11" style="color:var(--t2);text-align:center;padding:18px">Klikni "Osvježi"…</td></tr></tbody></table>
|
||||
<table id="up-tbl"><thead><tr><th>#</th><th>Datoteka</th><th class="num">Veličina</th><th>Vendor</th><th>OIB</th><th>Br. računa</th><th>Datum</th><th class="num">Brutto</th><th>OCR status</th><th class="num">Conf</th><th>Račun</th><th>Akcije</th></tr></thead><tbody><tr><td colspan="12" style="color:var(--t2);text-align:center;padding:18px">Klikni "Osvježi"…</td></tr></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -973,7 +983,7 @@ async function loadProracun(){
|
||||
// ===== INVOICE UPLOADS =====
|
||||
async function loadUploads(){
|
||||
const tbody = document.querySelector('#up-tbl tbody');
|
||||
tbody.innerHTML = `<tr><td colspan="11" style="color:var(--t2);text-align:center;padding:14px">Učitavam…</td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="12" style="color:var(--t2);text-align:center;padding:14px">Učitavam…</td></tr>`;
|
||||
try {
|
||||
const q = (document.getElementById('up-q').value||'').trim();
|
||||
const st = document.getElementById('up-status').value;
|
||||
@@ -994,13 +1004,55 @@ async function loadUploads(){
|
||||
<td><span class="badge ${r.ocr_status||''}">${r.ocr_status||'—'}</span></td>
|
||||
<td class="num">${r.ocr_confidence!=null?fmt(r.ocr_confidence)+' %':''}</td>
|
||||
<td>${r.invoice_id?('#'+r.invoice_id):'—'}</td>
|
||||
<td><a class="btn sec" href="/uploads/${r.file_path||''}" target="_blank">Otvori</a></td>
|
||||
</tr>`).join('')
|
||||
: `<tr><td colspan="11" style="color:var(--t2);text-align:center;padding:14px">Nema uploadova.</td></tr>`;
|
||||
: `<tr><td colspan="12" style="color:var(--t2);text-align:center;padding:14px">Nema uploadova.</td></tr>`;
|
||||
} catch(e) {
|
||||
tbody.innerHTML = `<tr><td colspan="11" style="color:var(--red)">Greška: ${e.message}</td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="12" style="color:var(--red)">Greška: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Upload new invoice file via multipart
|
||||
async function uploadInvoiceFile(file){
|
||||
if(!file) return;
|
||||
const prog = document.getElementById('up-progress');
|
||||
prog.textContent = 'Šaljem ' + file.name + ' (' + Math.round(file.size/1024) + ' KB)…';
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append('file', file);
|
||||
// Note: no Content-Type header — browser sets multipart boundary
|
||||
const r = await fetch(API + '/invoice-uploads', {
|
||||
method:'POST', body: fd, headers: AUTH()
|
||||
});
|
||||
if(!r.ok) throw new Error('HTTP ' + r.status + ' ' + (await r.text()));
|
||||
const j = await r.json();
|
||||
prog.textContent = '✓ Uploaded #' + j.id + ' (' + Math.round((j.file_size||0)/1024) + ' KB) — OCR pending.';
|
||||
document.getElementById('up-file').value = '';
|
||||
loadUploads();
|
||||
} catch(e) {
|
||||
prog.textContent = '✗ Greška: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
// Drag & drop on uploads card
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const drop = document.getElementById('up-drop');
|
||||
if(!drop) return;
|
||||
['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, e => {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
drop.style.background='var(--bg2)';
|
||||
}));
|
||||
['dragleave','drop'].forEach(ev => drop.addEventListener(ev, e => {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
drop.style.background='var(--bg3)';
|
||||
}));
|
||||
drop.addEventListener('drop', e => {
|
||||
if(e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]){
|
||||
uploadInvoiceFile(e.dataTransfer.files[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ===== PUTNI NALOZI / EXPENSE REPORTS =====
|
||||
async function loadExpenseReports(){
|
||||
const tbody = document.querySelector('#pn-tbl tbody');
|
||||
@@ -1148,10 +1200,32 @@ const loaders = {
|
||||
kontni: loadKontniPlan
|
||||
};
|
||||
|
||||
// Switch programmatically (used by deep links: ?tab=uploads / #tab=putni)
|
||||
function activateTab(panelId){
|
||||
const t = document.querySelector('.tab[data-panel="' + panelId + '"]');
|
||||
if(!t) return false;
|
||||
t.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initial
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadKontoCache();
|
||||
loadPartnerCache();
|
||||
// Deep-link support: ?tab=<panel> or #tab=<panel>
|
||||
let target = null;
|
||||
try {
|
||||
const u = new URL(window.location.href);
|
||||
target = u.searchParams.get('tab');
|
||||
if(!target && u.hash){
|
||||
const m = u.hash.match(/tab=([a-z]+)/i);
|
||||
if(m) target = m[1];
|
||||
}
|
||||
} catch(e) {}
|
||||
if(target && activateTab(target)){
|
||||
// tab.click() already triggers loader
|
||||
return;
|
||||
}
|
||||
loadDnevnik();
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user