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:
+238
-49
@@ -512,8 +512,10 @@ const NAV_BY_ROLE = {
|
||||
{id:'erp', ic:'\u{1F4BC}', label:'ERP', href:'/erp/full'},
|
||||
{id:'crm', ic:'\u{1F4DD}', label:'CRM', href:'/crm/v2'},
|
||||
{id:'dokumenti', ic:'\u{1F4D6}', label:'Dokumenti'},
|
||||
{id:'racuni', ic:'\u{1F9FE}', label:'Računi (OCR)'},
|
||||
{id:'racuni', ic:'\u{1F9FE}', label:'Računi (OCR)', href:'/erp/full?tab=uploads'},
|
||||
{id:'putni', ic:'\u{2708}', label:'Putni nalozi', href:'/erp/full?tab=putni'},
|
||||
{id:'kalendar', ic:'\u{1F4C5}', label:'Kalendar'},
|
||||
{id:'notif', ic:'\u{1F514}', label:'Notifikacije'},
|
||||
{id:'audit', ic:'\u{1F50D}', label:'Audit log'},
|
||||
{id:'forenzika', ic:'⚠', label:'Forenzika', badge:11},
|
||||
],
|
||||
@@ -524,8 +526,10 @@ const NAV_BY_ROLE = {
|
||||
{id:'sportasi', ic:'\u{1F464}', label:'Naši sportaši'},
|
||||
{id:'zahtjevi', ic:'\u{1F4D1}', label:'Zahtjevi PGŽ', badge:3},
|
||||
{id:'kalendar', ic:'\u{1F4C5}', label:'Kalendar'},
|
||||
{id:'notif', ic:'\u{1F514}', label:'Notifikacije'},
|
||||
{id:'lijecnicki',ic:'⚕', label:'Liječnički'},
|
||||
{id:'racuni', ic:'\u{1F9FE}', label:'Računi (OCR)'},
|
||||
{id:'racuni', ic:'\u{1F9FE}', label:'Računi (OCR)', href:'/erp/full?tab=uploads'},
|
||||
{id:'putni', ic:'\u{2708}', label:'Putni nalozi', href:'/erp/full?tab=putni'},
|
||||
],
|
||||
klub: [
|
||||
{id:'profil', ic:'\u{1F464}', label:'Moj profil'},
|
||||
@@ -535,8 +539,10 @@ const NAV_BY_ROLE = {
|
||||
{id:'lijecnicki',ic:'⚕', label:'Liječnički'},
|
||||
{id:'dokumenti', ic:'\u{1F4C4}', label:'Dokumenti'},
|
||||
{id:'kalendar', ic:'\u{1F4C5}', label:'Kalendar'},
|
||||
{id:'notif', ic:'\u{1F514}', label:'Notifikacije'},
|
||||
{id:'manifestacije', ic:'\u{1F4C5}', label:'Manifestacije'},
|
||||
{id:'racuni', ic:'\u{1F9FE}', label:'Računi (OCR)'},
|
||||
{id:'racuni', ic:'\u{1F9FE}', label:'Računi (OCR)', href:'/erp/full?tab=uploads'},
|
||||
{id:'putni', ic:'\u{2708}', label:'Putni nalozi', href:'/erp/full?tab=putni'},
|
||||
],
|
||||
sportas: [
|
||||
{id:'profil', ic:'\u{1F464}', label:'Moj profil'},
|
||||
@@ -546,6 +552,7 @@ const NAV_BY_ROLE = {
|
||||
{id:'dokumenti', ic:'\u{1F4C4}', label:'Moji dokumenti'},
|
||||
{id:'obrasci', ic:'\u{1F4DD}', label:'Obrasci', badge:1},
|
||||
{id:'kalendar', ic:'\u{1F4C5}', label:'Kalendar'},
|
||||
{id:'notif', ic:'\u{1F514}', label:'Notifikacije'},
|
||||
{id:'manifestacije', ic:'\u{1F4C5}', label:'Manifestacije'},
|
||||
],
|
||||
};
|
||||
@@ -754,13 +761,16 @@ function setRole(r){
|
||||
//=========== NAV ===========
|
||||
function buildNav(){
|
||||
const items = NAV_BY_ROLE[_state.role] || [];
|
||||
$('#nav').innerHTML = items.map(n =>
|
||||
`<div class="nav-i ${n.id===_state.section?'active':''}" data-id="${n.id}" data-label="${esc(n.label)}" onclick="navTo('${n.id}')">
|
||||
$('#nav').innerHTML = items.map((n, idx) => {
|
||||
const click = n.href
|
||||
? `onclick="window.location.href='${n.href}'"`
|
||||
: `onclick="navTo('${n.id}')"`;
|
||||
return `<div class="nav-i ${n.id===_state.section?'active':''}" data-id="${n.id}" data-label="${esc(n.label)}" ${click}>
|
||||
<span class="ic">${n.ic}</span>
|
||||
<span class="lbl">${esc(n.label)}</span>
|
||||
${n.badge?`<span class="badge">${n.badge}</span>`:''}
|
||||
</div>`
|
||||
).join('');
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
window.addEventListener('hashchange', () => {
|
||||
const h = (location.hash||'').replace(/^#/,'');
|
||||
@@ -802,9 +812,11 @@ const TITLES = {
|
||||
klubovi:['Klubovi','Sportski klubovi PGŽ'],
|
||||
sportasi:['Sportaši','Registrirani članovi'],
|
||||
financije:['Financije','Sufinanciranje sporta'],
|
||||
racuni:['Računi (OCR)','OCR upload + obrada'],
|
||||
racuni:['Računi (OCR)','OCR upload + obrada (ERP Full)'],
|
||||
putni:['Putni nalozi','Otvara ERP Full → Putni nalozi'],
|
||||
crm:['CRM','Članarine + liječnički'],
|
||||
kalendar:['Kalendar','Liječnički termini, manifestacije, eventi'],
|
||||
notif:['Notifikacije','Centar obavijesti — InApp poruke'],
|
||||
audit:['Audit log','Sve aktivnosti sustava'],
|
||||
forenzika:['Forenzika','Sumnjive transakcije / PEP'],
|
||||
},
|
||||
@@ -815,8 +827,10 @@ const TITLES = {
|
||||
sportasi:['Naši sportaši','Registrirani sportaši saveza'],
|
||||
zahtjevi:['Zahtjevi PGŽ','Sufinanciranje aktivnosti'],
|
||||
kalendar:['Kalendar','Manifestacije saveza'],
|
||||
notif:['Notifikacije','Centar obavijesti — InApp poruke'],
|
||||
lijecnicki:['Liječnički','Pregledi članova saveza'],
|
||||
racuni:['Računi','Računi saveza'],
|
||||
racuni:['Računi','Računi saveza (ERP Full)'],
|
||||
putni:['Putni nalozi','ERP Full → Putni nalozi'],
|
||||
},
|
||||
klub: {
|
||||
profil:['Moj profil','Osobni podaci'],
|
||||
@@ -826,8 +840,10 @@ const TITLES = {
|
||||
lijecnicki:['Liječnički','Pregledi članova'],
|
||||
dokumenti:['Dokumenti','Dokumenti kluba'],
|
||||
kalendar:['Kalendar','Liječnički termini + manifestacije'],
|
||||
notif:['Notifikacije','Centar obavijesti — InApp poruke'],
|
||||
manifestacije:['Manifestacije','Nadolazeće aktivnosti'],
|
||||
racuni:['Računi','Troškovi kluba'],
|
||||
racuni:['Računi','Troškovi kluba (ERP Full)'],
|
||||
putni:['Putni nalozi','ERP Full → Putni nalozi'],
|
||||
},
|
||||
sportas: {
|
||||
profil:['Moj profil','Osobni podaci'],
|
||||
@@ -837,6 +853,7 @@ const TITLES = {
|
||||
dokumenti:['Moji dokumenti','Suglasnosti, ugovori'],
|
||||
obrasci:['Obrasci','Za potpis'],
|
||||
kalendar:['Kalendar','Moji termini i događaji'],
|
||||
notif:['Notifikacije','Centar obavijesti — InApp poruke'],
|
||||
manifestacije:['Manifestacije','Moje aktivnosti'],
|
||||
},
|
||||
};
|
||||
@@ -1164,7 +1181,7 @@ SECTIONS['pgz:dashboard'] = async () => {
|
||||
<div style="display:grid;gap:8px">
|
||||
<button class="btn primary" onclick="setRole('pgz');navTo('korisnici')">+ Dodaj korisnika</button>
|
||||
<button class="btn" onclick="navTo('forenzika')">⚠ Pregled forenzike</button>
|
||||
<button class="btn" onclick="navTo('racuni')">🧾 Skeniraj račun (OCR)</button>
|
||||
<button class="btn" onclick="window.location.href='/erp/full?tab=uploads'">🧾 Skeniraj račun (OCR)</button>
|
||||
<button class="btn" onclick="navTo('audit')">🔍 Audit log</button>
|
||||
<button class="btn gold" onclick="window.open('/sport/','_blank')">🌐 Public portal</button>
|
||||
</div>
|
||||
@@ -1309,22 +1326,22 @@ SECTIONS['pgz:financije'] = async () => {
|
||||
</div>`;
|
||||
};
|
||||
|
||||
SECTIONS['pgz:racuni'] = () => `
|
||||
<div class="card">
|
||||
<div class="card-h"><div class="card-t">🧾 OCR upload (drag & drop)</div></div>
|
||||
<div style="border:2px dashed var(--rim2);border-radius:8px;padding:40px;text-align:center;background:var(--bg3);cursor:pointer" onclick="alert('OCR upload — backend M5 (CC4)')">
|
||||
<div style="font-size:48px;margin-bottom:8px">📷</div>
|
||||
<div style="font-weight:700;color:var(--t0);margin-bottom:4px">Dovuci ovdje sliku ili PDF računa</div>
|
||||
<div style="font-size:11px;color:var(--t2)">ili klikni za odabir · cestarina, gorivo, hotel, dnevnice...</div>
|
||||
<button class="btn primary" style="margin-top:12px">📸 Snimi kamerom</button>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--t4);margin-top:10px">Backend: Tesseract OCR + Ri.NET AI Engine ekstrakcija polja → invoices DB</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-h"><div class="card-t">📋 Nedavni računi</div></div>
|
||||
<table><thead><tr><th>Datum</th><th>Izdavatelj</th><th>OIB</th><th>Vrsta</th><th class="num">Iznos</th><th>Status</th></tr></thead>
|
||||
<tbody>${MOCK.invoices.map(r => `<tr><td>${esc(r.datum)}</td><td><b>${esc(r.izdavatelj)}</b></td><td>${esc(formatOib(r.oib))}</td><td><span class="tag ${r.tag}">${esc(r.vrsta)}</span></td><td class="num">${fmtEur(r.iznos)}</td><td>${esc(r.status)}</td></tr>`).join('')}</tbody></table>
|
||||
// Računi (OCR) je premješten u ERP Full → tab Uploads (consolidation 2026-05-05).
|
||||
// Legacy entry preusmjerava korisnika.
|
||||
SECTIONS['pgz:racuni'] = () => {
|
||||
setTimeout(() => { window.location.href = '/erp/full?tab=uploads'; }, 50);
|
||||
return `<div class="card"><div class="card-h"><div class="card-t">🧾 Računi (OCR) — premješteno u ERP Full</div></div>
|
||||
<p style="color:var(--t2);margin:8px 0">Otvaranje <b>ERP Full → Uploads (OCR)</b>…</p>
|
||||
<a class="btn primary" href="/erp/full?tab=uploads" style="text-decoration:none">📎 Otvori sada</a>
|
||||
</div>`;
|
||||
};
|
||||
SECTIONS['pgz:putni'] = () => {
|
||||
setTimeout(() => { window.location.href = '/erp/full?tab=putni'; }, 50);
|
||||
return `<div class="card"><div class="card-h"><div class="card-t">✈ Putni nalozi — premješteno u ERP Full</div></div>
|
||||
<p style="color:var(--t2);margin:8px 0">Otvaranje <b>ERP Full → Putni nalozi</b>…</p>
|
||||
<a class="btn primary" href="/erp/full?tab=putni" style="text-decoration:none">✈ Otvori sada</a>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
SECTIONS['pgz:crm'] = () => `
|
||||
<div style="margin-bottom:12px">
|
||||
@@ -1367,24 +1384,192 @@ SECTIONS['pgz:audit'] = () => `
|
||||
|
||||
// =======================================================================
|
||||
// CC5 R5 — KALENDAR (liječnički termini + manifestacije + eventi)
|
||||
// Agent B 2026-05-05: Added CRUD on pgz_sport.kalendar_events
|
||||
// =======================================================================
|
||||
|
||||
// ─── KALENDAR CRUD helpers ─────────────────────────────────────────────
|
||||
window._kalState = window._kalState || { events: [], ym: null };
|
||||
|
||||
async function kalLoadEvents(ym){
|
||||
const [Y, M] = ym.split('-').map(Number);
|
||||
const from = `${Y}-${String(M).padStart(2,'0')}-01`;
|
||||
const nm = M===12 ? {y:Y+1,m:1} : {y:Y,m:M+1};
|
||||
const to = `${nm.y}-${String(nm.m).padStart(2,'0')}-01`;
|
||||
try {
|
||||
const d = await apiAuth(`/v2/kalendar/events?from=${from}&to=${to}`);
|
||||
return (d && d.rows) || [];
|
||||
} catch(e){ return []; }
|
||||
}
|
||||
|
||||
function _kalFmtLocal(iso){
|
||||
if(!iso) return '';
|
||||
const d = new Date(iso);
|
||||
if(isNaN(d.getTime())) return '';
|
||||
const pad = n => String(n).padStart(2,'0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
function kalEventModalHtml(ev){
|
||||
ev = ev || {};
|
||||
const isEdit = !!ev.id;
|
||||
return `
|
||||
<div id="kalModalBg" style="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center" onclick="if(event.target===this) kalCloseModal()">
|
||||
<div style="background:var(--bg2);border:1px solid var(--rim);border-radius:8px;width:min(560px,92vw);max-height:90vh;overflow:auto">
|
||||
<div style="padding:14px 16px;border-bottom:1px solid var(--rim);display:flex;justify-content:space-between;align-items:center">
|
||||
<div style="font-weight:600;font-size:15px">${isEdit?'Uredi termin #'+ev.id:'Novi termin'}</div>
|
||||
<button class="btn sm" onclick="kalCloseModal()">✕</button>
|
||||
</div>
|
||||
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:10px">
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--t3);text-transform:uppercase;font-weight:600">
|
||||
Naziv *
|
||||
<input id="kalF_title" value="${esc(ev.title||'')}" style="background:var(--bg1);border:1px solid var(--rim);color:var(--t1);padding:8px;border-radius:4px;font-size:13px">
|
||||
</label>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--t3);text-transform:uppercase;font-weight:600">
|
||||
Početak *
|
||||
<input id="kalF_start" type="datetime-local" value="${_kalFmtLocal(ev.start_at)}" style="background:var(--bg1);border:1px solid var(--rim);color:var(--t1);padding:8px;border-radius:4px;font-size:13px">
|
||||
</label>
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--t3);text-transform:uppercase;font-weight:600">
|
||||
Kraj
|
||||
<input id="kalF_end" type="datetime-local" value="${_kalFmtLocal(ev.end_at)}" style="background:var(--bg1);border:1px solid var(--rim);color:var(--t1);padding:8px;border-radius:4px;font-size:13px">
|
||||
</label>
|
||||
</div>
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--t3);text-transform:uppercase;font-weight:600">
|
||||
Lokacija
|
||||
<input id="kalF_loc" value="${esc(ev.location||'')}" style="background:var(--bg1);border:1px solid var(--rim);color:var(--t1);padding:8px;border-radius:4px;font-size:13px">
|
||||
</label>
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--t3);text-transform:uppercase;font-weight:600">
|
||||
Opis
|
||||
<textarea id="kalF_desc" rows="3" style="background:var(--bg1);border:1px solid var(--rim);color:var(--t1);padding:8px;border-radius:4px;font-size:13px;resize:vertical">${esc(ev.description||'')}</textarea>
|
||||
</label>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--t3);text-transform:uppercase;font-weight:600">
|
||||
Tip
|
||||
<select id="kalF_type" style="background:var(--bg1);border:1px solid var(--rim);color:var(--t1);padding:8px;border-radius:4px;font-size:13px">
|
||||
${['event','meeting','manif','training','medical','other'].map(t => `<option value="${t}" ${ev.event_type===t?'selected':''}>${t}</option>`).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--t3);text-transform:uppercase;font-weight:600">
|
||||
Boja
|
||||
<select id="kalF_color" style="background:var(--bg1);border:1px solid var(--rim);color:var(--t1);padding:8px;border-radius:4px;font-size:13px">
|
||||
<option value="b" ${(!ev.color||ev.color==='b')?'selected':''}>plava</option>
|
||||
<option value="g" ${ev.color==='g'?'selected':''}>zelena</option>
|
||||
<option value="a" ${ev.color==='a'?'selected':''}>amber</option>
|
||||
<option value="r" ${ev.color==='r'?'selected':''}>crvena</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:12px 16px;border-top:1px solid var(--rim);display:flex;justify-content:space-between;gap:8px">
|
||||
<div>
|
||||
${isEdit ? `<button class="btn sm" style="background:rgba(220,38,38,0.18);border-color:rgba(220,38,38,0.5);color:#fca5a5" onclick="kalDelete(${ev.id})">🗑 Obriši</button>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn sm" onclick="kalCloseModal()">Odustani</button>
|
||||
<button class="btn primary sm" onclick="kalSave(${ev.id||'null'})">${isEdit?'Spremi':'Kreiraj'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function kalOpenModal(ev){
|
||||
const wrap = document.createElement('div');
|
||||
wrap.id = 'kalModalWrap';
|
||||
wrap.innerHTML = kalEventModalHtml(ev);
|
||||
document.body.appendChild(wrap);
|
||||
setTimeout(() => { try { document.getElementById('kalF_title').focus(); } catch(e){} }, 50);
|
||||
}
|
||||
|
||||
function kalCloseModal(){
|
||||
const w = document.getElementById('kalModalWrap');
|
||||
if(w) w.remove();
|
||||
}
|
||||
|
||||
async function kalRefresh(){
|
||||
const ym = window._kalState.ym || (new Date().getFullYear()+'-'+String(new Date().getMonth()+1).padStart(2,'0'));
|
||||
$('#content').innerHTML = '<div class=loading>Učitavanje...</div>';
|
||||
$('#content').innerHTML = await renderKalendar({ym});
|
||||
}
|
||||
|
||||
async function kalSave(eid){
|
||||
const title = (document.getElementById('kalF_title').value||'').trim();
|
||||
const start = document.getElementById('kalF_start').value;
|
||||
const end = document.getElementById('kalF_end').value;
|
||||
const loc = document.getElementById('kalF_loc').value;
|
||||
const desc = document.getElementById('kalF_desc').value;
|
||||
const typ = document.getElementById('kalF_type').value;
|
||||
const col = document.getElementById('kalF_color').value;
|
||||
if(!title){ alert('Naziv je obavezan.'); return; }
|
||||
if(!start){ alert('Početak je obavezan.'); return; }
|
||||
const toIso = (s) => s ? new Date(s).toISOString() : null;
|
||||
const body = {
|
||||
title,
|
||||
start_at: toIso(start),
|
||||
end_at: end ? toIso(end) : null,
|
||||
location: loc || null,
|
||||
description: desc || null,
|
||||
event_type: typ,
|
||||
color: col,
|
||||
};
|
||||
let res;
|
||||
if(eid && eid !== 'null'){
|
||||
res = await apiAuth('/v2/kalendar/events/'+eid, {method:'PUT', body: JSON.stringify(body)});
|
||||
} else {
|
||||
res = await apiAuth('/v2/kalendar/events', {method:'POST', body: JSON.stringify(body)});
|
||||
}
|
||||
if(!res || res.__error || res.__unauthorized){
|
||||
alert('Greška pri spremanju (status '+(res && res.status || '?')+').');
|
||||
return;
|
||||
}
|
||||
kalCloseModal();
|
||||
await kalRefresh();
|
||||
}
|
||||
|
||||
async function kalEdit(eid){
|
||||
const r = await apiAuth('/v2/kalendar/events/'+eid);
|
||||
if(!r || r.__error){ alert('Ne mogu dohvatiti termin.'); return; }
|
||||
kalOpenModal(r);
|
||||
}
|
||||
|
||||
async function kalDelete(eid){
|
||||
if(!confirm('Obrisati termin #'+eid+'?')) return;
|
||||
const r = await apiAuth('/v2/kalendar/events/'+eid, {method:'DELETE'});
|
||||
if(!r || r.__error){ alert('Greška pri brisanju.'); return; }
|
||||
kalCloseModal();
|
||||
await kalRefresh();
|
||||
}
|
||||
|
||||
async function renderKalendar(opts){
|
||||
opts = opts || {};
|
||||
const today = new Date();
|
||||
const ym = opts.ym || (today.getFullYear() + '-' + String(today.getMonth()+1).padStart(2,'0'));
|
||||
window._kalState.ym = ym;
|
||||
const [Y, M] = ym.split('-').map(Number);
|
||||
const first = new Date(Y, M-1, 1);
|
||||
const last = new Date(Y, M, 0);
|
||||
|
||||
// Učitaj sve liječničke koji ističu unutar +180 dana, manifestacije iz API-ja, i mock eventove
|
||||
let lij = [], manif = [], notif = [];
|
||||
// Učitaj liječničke + manifestacije + notifikacije + kalendar_events
|
||||
let lij = [], manif = [], notif = [], kalEvents = [];
|
||||
try { const d = await fetch('/sport/api/crm/lijecnicki/uskoro-isticu?days=180&include_expired=false').then(r=>r.json()); lij = d.rows || []; } catch(e){}
|
||||
try { const d = await fetch('/sport/api/manifestacije').then(r=>r.json()); manif = d.rows || d || []; } catch(e){}
|
||||
try { const d = await fetch('/sport/api/crm/notifications?limit=50').then(r=>r.json()); notif = d.rows || []; } catch(e){}
|
||||
kalEvents = await kalLoadEvents(ym);
|
||||
window._kalState.events = kalEvents;
|
||||
|
||||
const events = [];
|
||||
lij.forEach(l => events.push({date: l.vrijedi_do, type:'lij', title:`⚕ Pregled ističe: ${l.clan}`, klub:l.klub, color:'a'}));
|
||||
manif.forEach(m => { if (m.datum) events.push({date: m.datum, type:'manif', title:`📅 ${m.naziv || m.title || 'Manifestacija'}`, klub:m.lokacija || m.grad, color:'b'}); });
|
||||
// CRUD events from kalendar_events table
|
||||
kalEvents.forEach(k => events.push({
|
||||
id: k.id,
|
||||
date: (k.start_at||'').substring(0,10),
|
||||
type: k.event_type || 'event',
|
||||
title: k.title,
|
||||
klub: k.location || '',
|
||||
color: k.color || 'b',
|
||||
crud: true,
|
||||
}));
|
||||
// Eventi: ZZJZ termini mock — sljedećih 7 dana po radnim danima
|
||||
for(let d=0; d<14; d++){
|
||||
const dt = new Date(); dt.setDate(dt.getDate()+d);
|
||||
@@ -1423,16 +1608,27 @@ async function renderKalendar(opts){
|
||||
const k = `${Y}-${String(M).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
|
||||
const ev = byDate[k] || [];
|
||||
const isToday = (k === today.toISOString().slice(0,10));
|
||||
const evHtml = ev.slice(0,3).map(e => `<div style="font-size:10px;background:rgba(${e.color==='a'?'245,158,11':e.color==='b'?'26,115,232':'34,197,94'},0.18);padding:2px 4px;border-radius:3px;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(e.title)}${e.klub?' — '+esc(e.klub):''}">${esc(e.title.substring(0,28))}</div>`).join('');
|
||||
const evHtml = ev.slice(0,3).map(e => {
|
||||
const click = e.crud && e.id ? `onclick="event.stopPropagation();kalEdit(${e.id})"` : '';
|
||||
const cur = e.crud ? 'cursor:pointer;' : '';
|
||||
return `<div ${click} style="${cur}font-size:10px;background:rgba(${e.color==='a'?'245,158,11':e.color==='b'?'26,115,232':'34,197,94'},0.18);padding:2px 4px;border-radius:3px;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(e.title)}${e.klub?' — '+esc(e.klub):''}${e.crud?' (klikni za uredi)':''}">${esc(e.title.substring(0,28))}</div>`;
|
||||
}).join('');
|
||||
const more = ev.length > 3 ? `<div style="font-size:9px;color:var(--t3);margin-top:2px">+${ev.length-3} više</div>` : '';
|
||||
grid += `<div style="background:${isToday?'rgba(26,115,232,0.15)':'var(--bg2)'};border:1px solid ${isToday?'var(--pgz-blue)':'var(--rim)'};border-radius:6px;padding:6px;min-height:90px;${ev.length?'cursor:pointer':''}" ><div style="font-weight:600;font-size:13px;color:${isToday?'var(--pgz-blue)':'var(--t1)'}">${d}</div>${evHtml}${more}</div>`;
|
||||
// Click on empty cell → create event for that date at 09:00
|
||||
const cellClick = `onclick="kalOpenModal({start_at:'${k}T09:00:00'})"`;
|
||||
grid += `<div ${cellClick} style="background:${isToday?'rgba(26,115,232,0.15)':'var(--bg2)'};border:1px solid ${isToday?'var(--pgz-blue)':'var(--rim)'};border-radius:6px;padding:6px;min-height:90px;cursor:pointer" title="Klik za novi termin"><div style="font-weight:600;font-size:13px;color:${isToday?'var(--pgz-blue)':'var(--t1)'}">${d}</div>${evHtml}${more}</div>`;
|
||||
}
|
||||
grid += '</div>';
|
||||
|
||||
// Lista nadolazećih (top 10)
|
||||
const upcoming = events.filter(e => e.date && e.date >= today.toISOString().slice(0,10))
|
||||
.sort((a,b) => a.date.localeCompare(b.date)).slice(0, 10);
|
||||
const upcomingHtml = upcoming.map(e => `<tr><td>${esc(e.date)}</td><td>${esc(e.title)}</td><td>${esc(e.klub||'—')}</td><td><span class="tag ${e.color==='a'?'am':e.color==='b'?'bl':'gr'}">${e.type}</span></td></tr>`).join('');
|
||||
const upcomingHtml = upcoming.map(e => {
|
||||
const actions = e.crud && e.id
|
||||
? `<button class="btn sm" onclick="kalEdit(${e.id})">Uredi</button> <button class="btn sm" style="color:#fca5a5" onclick="kalDelete(${e.id})">🗑</button>`
|
||||
: '<span style="color:var(--t3);font-size:11px">—</span>';
|
||||
return `<tr><td>${esc(e.date)}</td><td>${esc(e.title)}</td><td>${esc(e.klub||'—')}</td><td><span class="tag ${e.color==='a'?'am':e.color==='b'?'bl':'gr'}">${e.type}</span></td><td>${actions}</td></tr>`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<div class="kpi-grid" style="margin-bottom:12px">
|
||||
@@ -1448,16 +1644,19 @@ async function renderKalendar(opts){
|
||||
<button class="btn sm" onclick="$('#content').innerHTML='<div class=loading>Učitavanje...</div>';renderKalendar({ym:'${prevYm}'}).then(h=>$('#content').innerHTML=h)">←</button>
|
||||
<input type="month" value="${ym}" onchange="$('#content').innerHTML='<div class=loading>...</div>';renderKalendar({ym:this.value}).then(h=>$('#content').innerHTML=h)" style="background:var(--bg2);border:1px solid var(--rim);color:var(--t1);padding:4px 8px;border-radius:4px">
|
||||
<button class="btn sm" onclick="$('#content').innerHTML='<div class=loading>Učitavanje...</div>';renderKalendar({ym:'${nextYm}'}).then(h=>$('#content').innerHTML=h)">→</button>
|
||||
<button class="btn primary sm" onclick="fetch('/sport/api/crm/lijecnicki/notify-scan',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'}).then(r=>r.json()).then(d=>alert('Skenirano: '+d.created+' notifikacija kreirano'))">🔔 Scan isteke → notifikacije</button>
|
||||
<button class="btn primary sm" onclick="kalOpenModal({})">+ Novi termin</button>
|
||||
<button class="btn sm" onclick="fetch('/sport/api/crm/lijecnicki/notify-scan',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'}).then(r=>r.json()).then(d=>alert('Skenirano: '+d.created+' notifikacija kreirano'))">🔔 Scan isteke → notifikacije</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:14px">${grid}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-h"><div class="card-t">📋 Nadolazeći eventi (10)</div></div>
|
||||
<div class="card-h"><div class="card-t">📋 Nadolazeći eventi (10)</div>
|
||||
<div class="card-actions"><button class="btn primary sm" onclick="kalOpenModal({})">+ Novi termin</button></div>
|
||||
</div>
|
||||
<table>
|
||||
<thead><tr><th>Datum</th><th>Naziv</th><th>Lokacija/Klub</th><th>Tip</th></tr></thead>
|
||||
<tbody>${upcomingHtml || '<tr><td colspan="4" class="empty">Nema nadolazećih eventa.</td></tr>'}</tbody>
|
||||
<thead><tr><th>Datum</th><th>Naziv</th><th>Lokacija/Klub</th><th>Tip</th><th>Akcije</th></tr></thead>
|
||||
<tbody>${upcomingHtml || '<tr><td colspan="5" class="empty">Nema nadolazećih eventa.</td></tr>'}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card">
|
||||
@@ -1620,19 +1819,7 @@ SECTIONS['savez:zahtjevi'] = () => `
|
||||
</div>`).join('')}
|
||||
</div>`;
|
||||
|
||||
SECTIONS['savez:kalendar'] = () => `
|
||||
<div class="card"><div class="card-h"><div class="card-t">📅 Kalendar manifestacija — Svibanj 2026</div></div>
|
||||
<div class="cal-grid">
|
||||
${'PON UTO SRI ČET PET SUB NED'.split(' ').map(h => `<div class="cal-h">${h}</div>`).join('')}
|
||||
${[...Array(31)].map((_,i) => {
|
||||
const day = i+1;
|
||||
const ev = [4,11,18,25,9,16,30].includes(day);
|
||||
const today = day===5;
|
||||
return `<div class="cal-d ${today?'t':''} ${ev?'has-event':''}"><b>${day}</b>${ev?`<div style="font-size:9px;color:var(--pgz-gold);margin-top:2px">Trening / utakmica</div>`:''}</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
<div style="margin-top:14px;font-size:11px;color:var(--t2)">● Trening kamp Platak (4–6.5) · ● Liga PGŽ atletika (11.5) · ● Open senior (18.5) · ● Memorijalna utrka (25.5)</div>
|
||||
</div>`;
|
||||
// (Stari savez:kalendar mock obrisan — sada koristi renderKalendar s CRUD-om iz pgz_sport.kalendar_events)
|
||||
|
||||
SECTIONS['savez:lijecnicki'] = () => `
|
||||
<div class="card"><div class="card-h"><div class="card-t">⚕ Liječnički pregledi članova saveza</div><div class="card-actions"><button class="btn primary">📅 Bulk ZZJZ rezervacija</button></div></div>
|
||||
@@ -1646,6 +1833,7 @@ SECTIONS['savez:lijecnicki'] = () => `
|
||||
</div>`;
|
||||
|
||||
SECTIONS['savez:racuni'] = SECTIONS['pgz:racuni'];
|
||||
SECTIONS['savez:putni'] = SECTIONS['pgz:putni'];
|
||||
|
||||
// =======================================================================
|
||||
// KLUB ADMIN — Dashboard + sub-pages
|
||||
@@ -1685,7 +1873,7 @@ SECTIONS['klub:dashboard'] = () => {
|
||||
<div class="card-h"><div class="card-t">⚡ Brze akcije</div></div>
|
||||
<div style="display:grid;gap:8px">
|
||||
<button class="btn primary" onclick="navTo('clanovi')">+ Dodaj člana</button>
|
||||
<button class="btn gold" onclick="navTo('racuni')">🧾 Skeniraj račun (OCR)</button>
|
||||
<button class="btn gold" onclick="window.location.href='/erp/full?tab=uploads'">🧾 Skeniraj račun (OCR)</button>
|
||||
<button class="btn" onclick="navTo('clanarine')">€ Članarine + HUB-3</button>
|
||||
<button class="btn" onclick="navTo('lijecnicki')">⚕ Liječnički bulk ZZJZ</button>
|
||||
<button class="btn" onclick="alert('Obrazac sufinanciranja — M9')">📑 Predaj zahtjev PGŽ</button>
|
||||
@@ -1765,6 +1953,7 @@ SECTIONS['klub:manifestacije'] = () => `
|
||||
</div>`;
|
||||
|
||||
SECTIONS['klub:racuni'] = SECTIONS['pgz:racuni'];
|
||||
SECTIONS['klub:putni'] = SECTIONS['pgz:putni'];
|
||||
|
||||
// =======================================================================
|
||||
// SPORTAŠ — Dashboard + sub-pages
|
||||
|
||||
Reference in New Issue
Block a user