Računi
| Broj | Dobavljač | Klub | Iznos | Status | Datum |
|---|
@@ -469,6 +551,190 @@ function activateTab(name) {
if (name === 'reports') loadReports();
}
+// === M5: OCR upload (drag-and-drop) ===
+const ERP_API = '/api/erp';
+
+async function ocrLoadKlubSelectors() {
+ const sels = [document.getElementById('oc_klub'), document.getElementById('pn_klub')].filter(Boolean);
+ if (!sels.length) return;
+ // Use main API for klubovi list (admin-scoped)
+ const d = await fetch(`/api/klubovi?limit=400`).then(r => r.json()).catch(() => null);
+ if (!d) return;
+ const arr = Array.isArray(d) ? d : (d.rows || d.items || []);
+ const opts = '
' + arr.map(k => `
`).join('');
+ sels.forEach(s => { if (s) s.innerHTML = opts; });
+}
+
+let ocrParsed = null;
+let ocrUploadId = null;
+
+function ocrSetStatus(msg, color) {
+ const el = document.getElementById('ocrStatus');
+ if (el) { el.textContent = msg || ''; el.style.color = color || 'var(--text-2)'; }
+}
+
+async function ocrHandleFile(file) {
+ if (!file) return;
+ ocrSetStatus('⏳ Učitavam datoteku…', 'var(--yellow)');
+ const klubVal = document.getElementById('oc_klub')?.value || '';
+ const fd = new FormData();
+ fd.append('file', file);
+ if (klubVal) fd.append('klub_id', klubVal);
+ fd.append('tenant_id', currentTenant || 1);
+ fd.append('invoice_kind', document.getElementById('oc_kind')?.value || 'ostalo');
+ let r = await fetch(`${ERP_API}/ocr/upload`, {method: 'POST', body: fd});
+ if (!r.ok) { ocrSetStatus('❌ Upload pao: ' + r.status, 'var(--red)'); return; }
+ const j = await r.json();
+ ocrUploadId = j.upload_id;
+ ocrSetStatus(`✓ Uploaded (id=${ocrUploadId}, ${j.size} B). Pokrećem OCR + LLM ekstrakciju…`, 'var(--accent)');
+
+ const fd2 = new FormData();
+ fd2.append('upload_id', ocrUploadId);
+ fd2.append('use_llm', 'true');
+ r = await fetch(`${ERP_API}/ocr/parse`, {method: 'POST', body: fd2});
+ if (!r.ok) { ocrSetStatus('❌ Parse pao: ' + r.status, 'var(--red)'); return; }
+ const p = await r.json();
+ if (!p.ok) { ocrSetStatus('❌ ' + (p.error || 'Parse fail'), 'var(--red)'); return; }
+ ocrParsed = p.extracted || {};
+ document.getElementById('oc_vendor_name').value = ocrParsed.vendor_name || '';
+ document.getElementById('oc_vendor_oib').value = ocrParsed.vendor_oib || '';
+ document.getElementById('oc_invoice_no').value = ocrParsed.invoice_no || '';
+ document.getElementById('oc_invoice_date').value = ocrParsed.invoice_date || '';
+ document.getElementById('oc_amount_net').value = ocrParsed.amount_net ?? '';
+ document.getElementById('oc_amount_vat').value = ocrParsed.amount_vat ?? '';
+ document.getElementById('oc_amount_gross').value = ocrParsed.amount_gross ?? '';
+ document.getElementById('oc_vat_rate').value = ocrParsed.vat_rate ?? '';
+ document.getElementById('oc_iban').value = ocrParsed.iban || '';
+ document.getElementById('oc_kind').value = ocrParsed.category || 'ostalo';
+ document.getElementById('oc_currency').value = ocrParsed.currency || 'EUR';
+ document.getElementById('oc_description').value = ocrParsed.description || '';
+ document.getElementById('oc_raw').textContent = (p.raw_text_preview || '').slice(0, 4000);
+ document.getElementById('ocrResult').style.display = 'block';
+ ocrSetStatus(`✓ OCR ${p.ocr_method} (${p.raw_chars} znakova). Provjeri polja i klikni "Spremi račun".`, 'var(--green)');
+}
+
+function ocrInitDrop() {
+ const drop = document.getElementById('ocrDrop');
+ const inp = document.getElementById('ocrFile');
+ if (!drop || !inp) return;
+ drop.addEventListener('click', () => inp.click());
+ inp.addEventListener('change', e => { if (e.target.files[0]) ocrHandleFile(e.target.files[0]); });
+ ['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, e => { e.preventDefault(); drop.style.borderColor = 'var(--accent)'; }));
+ ['dragleave','drop'].forEach(ev => drop.addEventListener(ev, e => { e.preventDefault(); drop.style.borderColor = 'var(--border)'; }));
+ drop.addEventListener('drop', e => { e.preventDefault(); const f = e.dataTransfer.files[0]; if (f) ocrHandleFile(f); });
+ document.getElementById('ocCancel')?.addEventListener('click', () => {
+ document.getElementById('ocrResult').style.display = 'none';
+ ocrParsed = null; ocrUploadId = null; ocrSetStatus('');
+ inp.value = '';
+ });
+ document.getElementById('ocSave')?.addEventListener('click', async () => {
+ const klub = document.getElementById('oc_klub').value;
+ if (!klub) { document.getElementById('ocSaveStatus').textContent = 'Odaberi klub'; return; }
+ const body = {
+ klub_id: parseInt(klub),
+ tenant_id: currentTenant || 1,
+ upload_id: ocrUploadId,
+ invoice_kind: document.getElementById('oc_kind').value || 'ostalo',
+ invoice_no: document.getElementById('oc_invoice_no').value,
+ vendor_name: document.getElementById('oc_vendor_name').value,
+ vendor_oib: document.getElementById('oc_vendor_oib').value,
+ invoice_date: document.getElementById('oc_invoice_date').value,
+ amount_net: parseFloat(document.getElementById('oc_amount_net').value) || null,
+ amount_vat: parseFloat(document.getElementById('oc_amount_vat').value) || null,
+ amount_gross: parseFloat(document.getElementById('oc_amount_gross').value),
+ vat_rate: parseFloat(document.getElementById('oc_vat_rate').value) || null,
+ iban_to: document.getElementById('oc_iban').value || null,
+ currency: document.getElementById('oc_currency').value || 'EUR',
+ category: document.getElementById('oc_kind').value || 'ostalo',
+ description: document.getElementById('oc_description').value || null,
+ };
+ document.getElementById('ocSaveStatus').textContent = '⏳ Spremam…';
+ const r = await fetch(`${ERP_API}/invoices`, {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body)});
+ const j = await r.json();
+ if (j.ok) {
+ document.getElementById('ocSaveStatus').textContent = `✓ Spremljen kao #${j.invoice.id}`;
+ document.getElementById('ocSaveStatus').style.color = 'var(--green)';
+ setTimeout(() => { document.getElementById('ocrResult').style.display = 'none'; loadERP(); }, 1500);
+ } else {
+ document.getElementById('ocSaveStatus').textContent = '❌ ' + (j.detail || 'Greška');
+ document.getElementById('ocSaveStatus').style.color = 'var(--red)';
+ }
+ });
+}
+
+// === M6: Putni nalog form with live dnevnice preview ===
+let pnPreviewTimer = null;
+async function pnRefreshPreview() {
+ const df = document.getElementById('pn_from')?.value;
+ const dt = document.getElementById('pn_to')?.value;
+ const country = document.getElementById('pn_country')?.value || 'Hrvatska';
+ const km = parseFloat(document.getElementById('pn_km')?.value || 0);
+ const km_rate = parseFloat(document.getElementById('pn_kmrate')?.value || 0.5);
+ const tgt = document.getElementById('pn_preview');
+ if (!df || !dt) { if (tgt) tgt.textContent = 'Unesi datume za live obračun dnevnica…'; return; }
+ const url = `${ERP_API}/putni-nalog/dnevnice/preview?date_from=${encodeURIComponent(df)}&date_to=${encodeURIComponent(dt)}&country=${encodeURIComponent(country)}&km=${km}&km_rate=${km_rate}`;
+ const r = await fetch(url).then(r => r.json()).catch(() => null);
+ if (!r || !r.ok) { tgt.textContent = '⚠ Neuspješan obračun'; return; }
+ const d = r.preview;
+ tgt.innerHTML = `
+
+
+
Pune dnevnice
${d.days_full} × €${d.rate_full}
+
Pola dnevnica
${d.days_half} × €${d.rate_half}
+
Dnevnice ukupno
€${d.dnevnica_amount_total}
+
Kilometara
${d.km_driven} km
+
Kilometrina
€${d.km_amount}
+
+
PROCJENA UKUPNO
€${d.total_estimated}
+
`;
+}
+
+function pnInit() {
+ ['pn_from','pn_to','pn_country','pn_km','pn_kmrate'].forEach(id => {
+ const el = document.getElementById(id);
+ if (el) el.addEventListener('input', () => {
+ clearTimeout(pnPreviewTimer);
+ pnPreviewTimer = setTimeout(pnRefreshPreview, 250);
+ });
+ });
+ document.getElementById('pnSave')?.addEventListener('click', async () => {
+ const klub = document.getElementById('pn_klub').value;
+ if (!klub) { document.getElementById('pnSaveStatus').textContent = 'Odaberi klub'; return; }
+ const body = {
+ klub_id: parseInt(klub),
+ tenant_id: currentTenant || 1,
+ voditelj_ime: document.getElementById('pn_voditelj').value,
+ putnici: (document.getElementById('pn_putnici').value || '').split(',').map(s => s.trim()).filter(Boolean),
+ svrha: document.getElementById('pn_svrha').value,
+ od_grada: document.getElementById('pn_od').value,
+ do_grada: document.getElementById('pn_do').value,
+ datum_polaska: document.getElementById('pn_from').value,
+ datum_povratka: document.getElementById('pn_to').value,
+ country: document.getElementById('pn_country').value,
+ vehicle_type: document.getElementById('pn_vehicle').value,
+ registracija_vozila: document.getElementById('pn_plate').value,
+ kilometara: parseFloat(document.getElementById('pn_km').value) || 0,
+ km_rate: parseFloat(document.getElementById('pn_kmrate').value) || 0.5,
+ };
+ document.getElementById('pnSaveStatus').textContent = '⏳ Spremam…';
+ const r = await fetch(`${ERP_API}/putni-nalog`, {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body)});
+ const j = await r.json();
+ if (j.ok) {
+ const pn = j.putni_nalog;
+ document.getElementById('pnSaveStatus').innerHTML = `✓ Putni nalog #${pn.id} kreiran (€${pn.cost_total})`;
+ document.getElementById('pnSaveStatus').style.color = 'var(--green)';
+ loadERP();
+ } else {
+ document.getElementById('pnSaveStatus').textContent = '❌ ' + (j.detail || 'Greška');
+ document.getElementById('pnSaveStatus').style.color = 'var(--red)';
+ }
+ });
+}
+
+ocrInitDrop();
+pnInit();
+ocrLoadKlubSelectors();
+
// Init
$$('.nav-item').forEach(n => n.addEventListener('click', () => activateTab(n.dataset.tab)));
diff --git a/static/admin_users.html b/static/admin_users.html
new file mode 100644
index 0000000..2133af1
--- /dev/null
+++ b/static/admin_users.html
@@ -0,0 +1,765 @@
+
+
+
+
+
+
PGŽ Sport · Admin · Korisnici
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Najnovije akcije zadnjih 10
+
| Vrijeme | Korisnik | Akcija | Resurs | IP |
|---|
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Lista korisnika —
+
+ | ID | E-mail | Ime | Uloga | Klub / Savez | Status | Zadnja prijava | Akcije |
+
+
+
+
+
+
+
+
+
Savezi
+
| ID | Naziv | Sport | Predsjednik | Tajnik |
|---|
+
+
Klubovi —
+
| ID | Naziv | Sport | Grad | OIB | Savez ID |
|---|
+
+
+
+
+
+
+
+
+
+
+
+
Događaji —
+
| Vrijeme | User | Akcija | Resurs | IP | UA | Meta |
|---|
+
+
+
+
+
+
+
Zaključani / failed-login računi
+
| E-mail | Uloga | Pokušaja | Zaključan do | Akcije |
|---|
+
+
Sesije
+
| — |
|---|
| Sesije se prate per-user kroz audit log (login.ok / logout / auth.refresh) |
+
+
+
+
+
+
+
Zahtjevi za brisanje Art. 17
+
| ID | Korisnik | E-mail | Razlog | Status | Zatraženo | Akcije |
|---|
+
+
Pristanak na kolačiće moja povijest
+
| Vrijeme | Session | Nužni | Analitički | Marketing | IP | Verzija |
|---|
+
+
+
+
+
+
+
+
+
+ Dodaj korisnika
+
+
+
+
+
+
+
+
Promjena lozinke
+
+
+
+
+
+
🍪 Kolačići
+
Koristimo nužne kolačiće za prijavu i sigurnost. Ostali kolačići samo uz vaše odobrenje.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/crm.html b/static/crm.html
new file mode 100644
index 0000000..dcac6b7
--- /dev/null
+++ b/static/crm.html
@@ -0,0 +1,974 @@
+
+
+
+
+
+
PGŽ Sport — CRM (Članarine • Liječnički • Obrasci)
+
+
+
+
+
+
⬢ PGŽ SPORT
+
·
+
CRM — Članarine • Liječnički • Obrasci
+
+
+
+
+
€ Članarine …
+
⚕ Liječnički pregledi …
+
📝 Obrasci …
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/erp.html b/static/erp.html
new file mode 100644
index 0000000..75febb5
--- /dev/null
+++ b/static/erp.html
@@ -0,0 +1,386 @@
+
+
+
+
+
+
PGŽ Sport · ERP — OCR + Putni nalozi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📷 Drag-and-drop OCR (PDF / JPG / PNG)
+
+
⤓
+
Povuci datoteku ovdje ili klikni za odabir
+
Tesseract OCR (hrv+eng) + DeepSeek V3 LLM ekstrakcija polja
+
+
+
+
+
+
+
+
Sirovi OCR tekst (preview)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Računi (svi klubovi)
+
| # | Vrsta | Broj | Dobavljač | OIB | Klub | Brutto | Status | Datum |
|---|
+
+
+
+
+
+
+
🚗 Novi putni nalog (HR pravilnik 2025)
+
+
+ Unesi datume za live obračun dnevnica…
+
+
+
+
+
+
+ HR pravilnik 2025: domaće 26.54 € (>8h), 13.27 € (5–8h), 0 € (<5h). Inozemne dnevnice po zemlji
+ (Italija/Austrija 35 €, Slovenija/Mađarska/BiH/Srbija 30 €). Kilometrina vlastitim automobilom 0.50 €/km.
+
+
+
+
+
+
+
+
Lista putnih naloga
+
| # | Klub | Destinacija | Polazak | Povratak | Dnevnice | Transport | Total | Status |
|---|
+
+
+
+
+
+
+
+
+
diff --git a/static/login.html b/static/login.html
new file mode 100644
index 0000000..14c8a5b
--- /dev/null
+++ b/static/login.html
@@ -0,0 +1,538 @@
+
+
+
+
+
+
PGŽ Sport · Prijava
+
+
+
+
+
+
+
+
+
+
P
+
+
PGŽ Sport
+
ERP/CRM Platforma
+
+
+
+
Operativna platforma za sport u Primorsko-goranskoj županiji.
+
Jedinstvena baza klubova, saveza i sportaša. Računovodstvo, članarine, liječnički pregledi, sufinanciranja — sve na jednom mjestu.
+
+
✓
Multi-tenant arhitektura — PGŽ, savezi, klubovi sa svojim view-om
+
✓
OCR za račune, automatska ekstrakcija polja, putni nalozi
+
✓
Članarine s HUB-3 uplatnicama i blockchain audit log
+
✓
GDPR-compliant (Art. 17, 20) · 2FA · audit svih akcija
+
+
+
+
+
+
+
+
Prijava
+
Unesite svoje podatke za pristup platformi.
+
+
+
+
+
+
Demo računi
+
+
+ PGŽ admin · damir@pgz.hr / PGZ2026!
+
+
+ Savez admin · pero@atletika.pgz.hr
+
+
+ Klub admin · ana@akkvarner.hr
+
+
+
+
+
+
+
+
+
+
🍪 Kolačići
+
Koristimo nužne kolačiće za prijavu i sigurnost sesije. Po vašem odobrenju koristimo i analitičke kolačiće za poboljšanje platforme. Više…
+
+
+
+
+
+
+
+
+
+
diff --git a/static/sport2.html b/static/sport2.html
index 607e86a..b8851f7 100644
--- a/static/sport2.html
+++ b/static/sport2.html
@@ -255,6 +255,7 @@ a.tag:hover,.tag[onclick]:hover{transform:translateY(-1px);filter:brightness(1.1
+