Task 4: Universal Export ▾ — CSV/XLSX/PDF dropdown across all screens
- routers/export_router.py: /api/v2/export?format=...&endpoint=...&filters=... - static/js/export_dropdown.js: shared attachExportDropdown helper - sport2/app/crm_v2/erp_full: Export ▾ button wired to representative tables - pgz_sport_api.py: mount export_router with try/except Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+175
-47
@@ -113,6 +113,12 @@ button,input,select{font-family:inherit;font-size:inherit;outline:none}
|
||||
.player-card .badge{font-size:9px;padding:2px 5px;border-radius:3px;background:var(--bg4);color:var(--t1);text-transform:uppercase;font-weight:600}
|
||||
.player-card .badge.repr{background:var(--pgz-gold);color:var(--bg0)}
|
||||
.player-card .badge.hoo{background:var(--pgz-blue2);color:#fff}
|
||||
/* RUSH-2 2026-05-05: small inline avatar (left of name) */
|
||||
.player-card .pn-row{display:flex;align-items:center;gap:8px}
|
||||
.player-card .pn-row .pn{flex:1;min-width:0}
|
||||
.rush2-avatar{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;overflow:hidden;background:var(--bg3);border:1px solid var(--rim);flex-shrink:0;color:var(--pgz-gold);font-weight:800;letter-spacing:.5px}
|
||||
.rush2-avatar img{width:100%;height:100%;object-fit:cover;display:block}
|
||||
.rush2-avatar.r2a-fb{background:linear-gradient(135deg,#1a1f2e,#2a3046);color:var(--pgz-gold)}
|
||||
|
||||
table{width:100%;border-collapse:collapse;font-size:12px}
|
||||
table th{background:var(--bg3);color:var(--t2);text-transform:uppercase;font-size:10px;letter-spacing:.5px;padding:8px 10px;text-align:left;border-bottom:1px solid var(--rim);font-weight:700}
|
||||
@@ -1403,6 +1409,7 @@ function renderSaveziShell(){
|
||||
</div>
|
||||
${window.renderPGZToggleBtn ? window.renderPGZToggleBtn('savezi') : ''}
|
||||
<span class="tb-s" id="sav-cnt"></span>
|
||||
<button id="sav-export-btn" class="export-btn" type="button">Export ▾</button>
|
||||
</div>
|
||||
<div id="sav-out"></div>
|
||||
`;
|
||||
@@ -1410,6 +1417,20 @@ function renderSaveziShell(){
|
||||
$('#sav-sport').addEventListener('change', applySaveziFilter);
|
||||
$('#sav-kat').addEventListener('change', applySaveziFilter);
|
||||
$('#sav-pgz').addEventListener('change', applySaveziFilter);
|
||||
// Export ▾ — uses same /v2/savezi/priority-sort URL as the table loader.
|
||||
if (window.attachExportDropdown) {
|
||||
window.attachExportDropdown(
|
||||
document.getElementById('sav-export-btn'),
|
||||
function(){
|
||||
const f = _filters.savezi || {};
|
||||
const useOnly = f.financirani || window._pgz_filter_priority;
|
||||
return '/sport/api' + (useOnly
|
||||
? '/v2/savezi/priority-sort?only=true&limit=500'
|
||||
: '/v2/savezi/priority-sort?only=false&limit=500');
|
||||
},
|
||||
'savezi'
|
||||
);
|
||||
}
|
||||
}
|
||||
function setSaveziView(v){
|
||||
_state.viewSavezi = v;
|
||||
@@ -1529,13 +1550,18 @@ async function loadKlubovi(){
|
||||
const root = $('#pg-klubovi');
|
||||
if(!_cache.klubovi){
|
||||
root.innerHTML = '<div class="loading">Učitavanje klubova…</div>';
|
||||
// BUG-E (2026-05-05): build /api/klubovi URL from explicit _filters.klubovi state.
|
||||
// Defaults: financirani=true + godisnjak=true. When BOTH off → load all.
|
||||
// RUSH-1 (2026-05-05): /api/klubovi URL built from _filters.klubovi state.
|
||||
// Spec (CC_FINAL_RUSH slika 4) — 3 checkboxes:
|
||||
// ☑ Samo financirani (PGŽ + RSS + Grad Rijeka) — single combined
|
||||
// ☑ U godišnjaku
|
||||
// ☐ Ima HNS roster
|
||||
// Backend `financiran=true` is OR of all 3 davateljs (single source of truth
|
||||
// = v_klubovi_financiranje view). Default = priority (fin OR godišnjak).
|
||||
// Sort: ukupno_potpora DESC.
|
||||
const f = _filters.klubovi;
|
||||
const qs = new URLSearchParams();
|
||||
qs.set('limit','2500');
|
||||
qs.set('sort','financiran'); qs.set('order','desc'); // sort by potpore DESC (financiran flag)
|
||||
// financirani + godisnjak combined with kategorija=priority logic:
|
||||
qs.set('sort','potpora'); qs.set('order','desc'); // ukupno_potpora DESC NULLS LAST
|
||||
if(f.financirani && f.godisnjak){
|
||||
qs.set('kategorija','priority'); // OR semantics → priority = financiran OR godišnjak
|
||||
} else if(f.financirani){
|
||||
@@ -1575,11 +1601,10 @@ function renderKluboviShell(){
|
||||
<button id="kl-card" class="${_state.viewKlubovi==='card'?'active':''}" onclick="setKluboviView('card')">Kartice</button>
|
||||
<button id="kl-table" class="${_state.viewKlubovi==='table'?'active':''}" onclick="setKluboviView('table')">Tablica</button>
|
||||
</div>
|
||||
<button class="btn" onclick="exportKlubovi('xlsx')">⬇ XLSX</button>
|
||||
<button class="btn" onclick="exportKlubovi('csv')">⬇ CSV</button>
|
||||
<button class="btn" onclick="enrichBulk('klub', 50, 70)">✨ Obogati (50)</button>
|
||||
${window.renderPGZToggleBtn ? window.renderPGZToggleBtn('klubovi') : ''}
|
||||
<span class="tb-s" id="kl-cnt"></span>
|
||||
<button id="kl-export-btn" class="export-btn" type="button">Export ▾</button>
|
||||
</div>
|
||||
<div id="kl-out"></div>
|
||||
`;
|
||||
@@ -1588,6 +1613,25 @@ function renderKluboviShell(){
|
||||
$('#kl-grad').addEventListener('change', applyKluboviFilter);
|
||||
$('#kl-kat').addEventListener('change', applyKluboviFilter);
|
||||
$('#kl-nk').addEventListener('change', applyKluboviFilter);
|
||||
// Export ▾ — rebuilds the same querystring that loadKlubovi uses.
|
||||
if (window.attachExportDropdown) {
|
||||
window.attachExportDropdown(
|
||||
document.getElementById('kl-export-btn'),
|
||||
function(){
|
||||
const f = _filters.klubovi || {};
|
||||
const qs = new URLSearchParams();
|
||||
qs.set('limit','2500');
|
||||
qs.set('sort','potpora'); qs.set('order','desc');
|
||||
if(f.financirani && f.godisnjak) qs.set('kategorija','priority');
|
||||
else if(f.financirani) qs.set('financiran','true');
|
||||
else if(f.godisnjak) qs.set('godisnjak','true');
|
||||
if(f.hns_roster) qs.set('samo_hns_roster','true');
|
||||
if(window._pgz_filter_priority && !qs.has('kategorija')) qs.set('kategorija','priority');
|
||||
return '/sport/api/klubovi?'+qs.toString();
|
||||
},
|
||||
'klubovi'
|
||||
);
|
||||
}
|
||||
}
|
||||
function setKluboviView(v){
|
||||
_state.viewKlubovi = v;
|
||||
@@ -1628,25 +1672,31 @@ function applyKluboviFilter(){
|
||||
}
|
||||
function renderKluboviGrid(rows){
|
||||
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
|
||||
return '<div class="grid-club">'+rows.map(k => `
|
||||
return '<div class="grid-club">'+rows.map(k => {
|
||||
const finTitle = [k.prima_pgz?'PGŽ':null, k.prima_rss?'RSS':null, k.prima_grad_rijeka?'Grad Rijeka':null].filter(Boolean).join(' + ') || 'financiran';
|
||||
const potpora = (k.ukupno_potpora!=null) ? ' <b style="color:var(--pgz-gold)" title="ukupno potpora">'+fmtEur(k.ukupno_potpora)+'</b>' : '';
|
||||
return `
|
||||
<div class="entity" onclick="openKlub(${k.id})">
|
||||
${k.priority?'<div class="et-tag" style="background:#ffd700;color:#1a1a1a">★ PRIO</div>':(k.nositelj_kvalitete?'<div class="et-tag">N.K.</div>':'')}
|
||||
<div class="et">${(window.pgzBadgePrefix?window.pgzBadgePrefix(k,'klub'):'')}${esc(k.klub||k.sport||'(bez naziva)')}</div>
|
||||
<div class="es">${txt(k.razina,'')} · ${txt(k.grad,'—')}</div>
|
||||
<div class="em">
|
||||
${k.financiran?'<span class="tag gd" title="PGŽ sufinanciran">€</span>':''}
|
||||
${k.financiran?'<span class="tag gd" title="'+esc(finTitle)+'">€</span>':''}
|
||||
${k.godisnjak?'<span class="tag b" title="U godišnjaku">G</span>':''}
|
||||
${potpora}
|
||||
<span><b>${fmtNum(k.registriranih)}</b> reg.</span>
|
||||
<span><b>${fmtNum(k.trenera)}</b> trenera</span>
|
||||
<span><b>${fmtNum(k.reprezentativaca)}</b> repr.</span>
|
||||
</div>
|
||||
</div>`).join('')+'</div>';
|
||||
</div>`;
|
||||
}).join('')+'</div>';
|
||||
}
|
||||
function renderKluboviTable(rows){
|
||||
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
|
||||
return `<div class="card" style="padding:0;overflow-x:auto"><table>
|
||||
<thead><tr><th style="width:34px"><input type="checkbox" id="kl-all" title="Označi sve"></th><th title="PGŽ priority">★</th>${sortHeader('klubovi','klub','Klub','')}${sortHeader('klubovi','sport','Sport','')}${sortHeader('klubovi','razina','Razina','')}${sortHeader('klubovi','grad','Grad','')}${sortHeader('klubovi','registriranih','Reg.','num')}${sortHeader('klubovi','trenera','Trenera','num')}${sortHeader('klubovi','nositelj_kvalitete','Status','')}</tr></thead>
|
||||
<tbody>${rows.map(k => `
|
||||
<thead><tr><th style="width:34px"><input type="checkbox" id="kl-all" title="Označi sve"></th><th title="PGŽ priority">★</th>${sortHeader('klubovi','klub','Klub','')}${sortHeader('klubovi','sport','Sport','')}${sortHeader('klubovi','razina','Razina','')}${sortHeader('klubovi','grad','Grad','')}${sortHeader('klubovi','ukupno_potpora','Potpora','num')}${sortHeader('klubovi','registriranih','Reg.','num')}${sortHeader('klubovi','nositelj_kvalitete','Status','')}</tr></thead>
|
||||
<tbody>${rows.map(k => {
|
||||
const finTitle = [k.prima_pgz?'PGŽ':null, k.prima_rss?'RSS':null, k.prima_grad_rijeka?'Grad Rijeka':null].filter(Boolean).join(' + ') || 'financiran';
|
||||
return `
|
||||
<tr>
|
||||
<td onclick="event.stopPropagation()"><input type="checkbox" class="kl-pick" data-id="${k.id}"></td>
|
||||
<td onclick="openKlub(${k.id})">${k.priority?'<span class="tag gd" title="financiran ili u godišnjaku">★</span>':''}</td>
|
||||
@@ -1654,10 +1704,11 @@ function renderKluboviTable(rows){
|
||||
<td onclick="openKlub(${k.id})">${txt(k.sport)}</td>
|
||||
<td onclick="openKlub(${k.id})">${txt(k.razina)}</td>
|
||||
<td onclick="openKlub(${k.id})">${txt(k.grad)}</td>
|
||||
<td onclick="openKlub(${k.id})" class="num"><b style="color:var(--pgz-gold)">${k.ukupno_potpora!=null?fmtEur(k.ukupno_potpora):'—'}</b></td>
|
||||
<td onclick="openKlub(${k.id})" class="num">${fmtNum(k.registriranih)}</td>
|
||||
<td onclick="openKlub(${k.id})" class="num">${fmtNum(k.trenera)}</td>
|
||||
<td onclick="openKlub(${k.id})">${k.financiran?'<span class="tag gd" title="financiran">€</span>':''}${k.godisnjak?'<span class="tag b" title="godišnjak">G</span>':''}${k.nositelj_kvalitete?'<span class="tag gd">N.K.</span>':''}${k.aktivan?'<span class="tag gr">AKT</span>':'<span class="tag rd">NK</span>'}</td>
|
||||
</tr>`).join('')}</tbody>
|
||||
<td onclick="openKlub(${k.id})">${k.financiran?'<span class="tag gd" title="'+esc(finTitle)+'">€</span>':''}${k.godisnjak?'<span class="tag b" title="godišnjak">G</span>':''}${k.nositelj_kvalitete?'<span class="tag gd">N.K.</span>':''}${k.aktivan?'<span class="tag gr">AKT</span>':'<span class="tag rd">NK</span>'}</td>
|
||||
</tr>`;
|
||||
}).join('')}</tbody>
|
||||
</table></div>`;
|
||||
}
|
||||
|
||||
@@ -2112,17 +2163,39 @@ function renderSportasiGrid(rows){
|
||||
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
|
||||
return '<div class="grid-player">'+rows.map(c => buildPlayerCard(c)).join('')+'</div>';
|
||||
}
|
||||
// RUSH-2 (2026-05-05): avatarUrl + avatarHTML helpers. Small circular avatar
|
||||
// to the left of the name in player cards (per Damir slika 6 spec).
|
||||
// Author: Damir Radulić (dradulic@outlook.com / damir@rinet.one)
|
||||
function avatarUrl(c){
|
||||
if(!c) return null;
|
||||
const u = c.slika_url || c.avatar || c.photo_url;
|
||||
if(!u) return null;
|
||||
if(/^https?:/i.test(u)) return u;
|
||||
if(u.startsWith('/')) return u;
|
||||
return '/sport/uploads/avatars/'+u;
|
||||
}
|
||||
function avatarHTML(c, sizePx){
|
||||
const sz = sizePx || 36;
|
||||
const initials = (((c.ime||'?')[0]||'?')+((c.prezime||'?')[0]||'?')).toUpperCase();
|
||||
const url = avatarUrl(c);
|
||||
if(url){
|
||||
return '<span class="rush2-avatar" style="width:'+sz+'px;height:'+sz+'px;font-size:'+Math.round(sz*0.4)+'px"><img src="'+esc(url)+'" alt="" onerror="this.style.display=\'none\';this.parentElement.classList.add(\'r2a-fb\');this.parentElement.innerHTML=\''+initials+'\'"></span>';
|
||||
}
|
||||
return '<span class="rush2-avatar r2a-fb" style="width:'+sz+'px;height:'+sz+'px;font-size:'+Math.round(sz*0.4)+'px">'+initials+'</span>';
|
||||
}
|
||||
function buildPlayerCard(c){
|
||||
const initials = (((c.ime||'?')[0]||'?')+((c.prezime||'?')[0]||'?')).toUpperCase();
|
||||
const photo = c.slika_url ? '<img src="'+esc(c.slika_url)+'" alt="" onerror="this.style.display=\'none\';if(this.parentElement)this.parentElement.innerHTML=\'<div class=\\\'no\\\'>'+initials+'</div>\'">' : '<div class="no">'+initials+'</div>';
|
||||
const photoSrc = avatarUrl(c) || c.slika_url;
|
||||
const photo = photoSrc ? '<img src="'+esc(photoSrc)+'" alt="" onerror="this.style.display=\'none\';if(this.parentElement)this.parentElement.innerHTML=\'<div class=\\\'no\\\'>'+initials+'</div>\'">' : '<div class="no">'+initials+'</div>';
|
||||
const hooCat = c.hoo_kategorija || c.kategorija_hoo;
|
||||
const smallAv = avatarHTML(c, 32);
|
||||
return `
|
||||
<div class="player-card" onclick="openSportas(${c.id})">
|
||||
<div class="ph">${photo}</div>
|
||||
<div class="pb">
|
||||
<div class="pn">${(window.pgzBadgePrefix?window.pgzBadgePrefix(c,'sportas'):'')}${esc(c.ime||'')} ${esc(c.prezime||'')}</div>
|
||||
<div class="pn-row">${smallAv}<div class="pn">${(window.pgzBadgePrefix?window.pgzBadgePrefix(c,'sportas'):'')}${esc(c.ime||'')} ${esc(c.prezime||'')}</div></div>
|
||||
<div class="pp">${txt(c.sport,'—')} · ${txt(c.pozicija,'')}</div>
|
||||
<div class="pk">${txt(c.klub_naziv_godisnjak,'')}</div>
|
||||
<div class="pk">${txt(c.klub_naziv_godisnjak||c.klub_naziv,'')}</div>
|
||||
<div class="badges">
|
||||
${c.reprezentativac?'<span class="badge repr">REPR</span>':''}
|
||||
${hooCat?'<span class="badge hoo">HOO '+esc(hooCat)+'</span>':''}
|
||||
@@ -2670,77 +2743,131 @@ function openObjekt(id){
|
||||
}
|
||||
|
||||
//=========== MANIFESTACIJE ===========
|
||||
// View mode persisted in localStorage as `_manifViewMode` ('card'|'table')
|
||||
const _manifFilter = {mjesto:'', razina:'', organizator:'', q:''};
|
||||
let _manifMeta = null;
|
||||
let _manifLoadSeq = 0;
|
||||
|
||||
async function loadManifestacije(){
|
||||
const root = $('#pg-manifestacije');
|
||||
if(!_cache.manifestacije){
|
||||
// Restore view mode from localStorage
|
||||
const saved = localStorage.getItem('_manifViewMode');
|
||||
if(saved==='card' || saved==='table') _state.viewManif = saved;
|
||||
if(!_manifMeta){
|
||||
root.innerHTML = '<div class="loading">Učitavanje manifestacija…</div>';
|
||||
const d = await api('/manifestacije-full');
|
||||
if(!d){ root.innerHTML='<div class="empty">Greška pri dohvatu</div>'; return; }
|
||||
_cache.manifestacije = d.rows || (Array.isArray(d) ? d : []);
|
||||
_manifMeta = await api('/v2/manifestacije/meta') || {mjesta:[], razine:[], organizatori:[]};
|
||||
}
|
||||
renderManifShell();
|
||||
applyManifFilter();
|
||||
await reloadManifestacije();
|
||||
}
|
||||
async function reloadManifestacije(){
|
||||
const seq = ++_manifLoadSeq;
|
||||
const out = $('#mn-out');
|
||||
if(out) out.innerHTML = '<div class="loading">Učitavanje…</div>';
|
||||
const cnt = $('#mn-cnt');
|
||||
if(cnt) cnt.textContent = '…';
|
||||
const params = new URLSearchParams();
|
||||
if(_manifFilter.mjesto) params.set('mjesto', _manifFilter.mjesto);
|
||||
if(_manifFilter.razina) params.set('razina', _manifFilter.razina);
|
||||
if(_manifFilter.organizator) params.set('organizator', _manifFilter.organizator);
|
||||
if(_manifFilter.q) params.set('q', _manifFilter.q);
|
||||
params.set('limit', '500');
|
||||
const qs = params.toString();
|
||||
const d = await api('/v2/manifestacije'+(qs?'?'+qs:''));
|
||||
if(seq !== _manifLoadSeq) return; // newer request superseded this one
|
||||
if(!d){
|
||||
if(out) out.innerHTML = '<div class="empty">Greška pri dohvatu</div>';
|
||||
return;
|
||||
}
|
||||
_cache.manifestacije = d.rows || [];
|
||||
renderManifBody();
|
||||
}
|
||||
function renderManifShell(){
|
||||
const root = $('#pg-manifestacije');
|
||||
const razine = Array.from(new Set((_cache.manifestacije||[]).map(m=>m.razina).filter(Boolean))).sort();
|
||||
const meta = _manifMeta || {mjesta:[], razine:[], organizatori:[]};
|
||||
const optList = (arr) => (arr||[]).filter(x=>x!==null && x!==undefined && x!=='').map(v=>'<option value="'+esc(v)+'">'+esc(v)+'</option>').join('');
|
||||
root.innerHTML = `
|
||||
<div class="toolbar">
|
||||
<input type="search" id="mn-q" placeholder="🔍 Pretraži manifestaciju…">
|
||||
<select id="mn-raz"><option value="">Sve razine</option>${razine.map(r=>'<option value="'+esc(r)+'">'+esc(r)+'</option>').join('')}</select>
|
||||
<input type="search" id="mn-q" placeholder="🔍 Pretraži manifestaciju…" value="${esc(_manifFilter.q)}">
|
||||
<select id="mn-mjesto" title="Mjesto"><option value="">Sva mjesta</option>${optList(meta.mjesta)}</select>
|
||||
<select id="mn-raz" title="Razina"><option value="">Sve razine</option>${optList(meta.razine)}</select>
|
||||
<select id="mn-org" title="Organizator"><option value="">Svi organizatori</option>${optList(meta.organizatori)}</select>
|
||||
<button id="mn-reset" class="btn" type="button" title="Poništi filtere">↺ Reset</button>
|
||||
<div class="toggle">
|
||||
<button id="mn-card" class="${_state.viewManif==='card'?'active':''}" onclick="setManifView('card')">Kartice</button>
|
||||
<button id="mn-table" class="${_state.viewManif==='table'?'active':''}" onclick="setManifView('table')">Tablica</button>
|
||||
<button id="mn-card" class="${_state.viewManif==='card'?'active':''}" onclick="setManifView('card')" title="Kartice">🃏 Kartice</button>
|
||||
<button id="mn-table" class="${_state.viewManif==='table'?'active':''}" onclick="setManifView('table')" title="Tablica">📋 Tablica</button>
|
||||
</div>
|
||||
<span class="tb-s" id="mn-cnt"></span>
|
||||
</div>
|
||||
<div id="mn-out"></div>
|
||||
`;
|
||||
$('#mn-q').addEventListener('input', debounce(applyManifFilter, 200));
|
||||
$('#mn-raz').addEventListener('change', applyManifFilter);
|
||||
// Restore selections after re-render
|
||||
if($('#mn-mjesto')) $('#mn-mjesto').value = _manifFilter.mjesto;
|
||||
if($('#mn-raz')) $('#mn-raz').value = _manifFilter.razina;
|
||||
if($('#mn-org')) $('#mn-org').value = _manifFilter.organizator;
|
||||
$('#mn-q').addEventListener('input', debounce(()=>{ _manifFilter.q = $('#mn-q').value.trim(); reloadManifestacije(); }, 250));
|
||||
$('#mn-mjesto').addEventListener('change', ()=>{ _manifFilter.mjesto = $('#mn-mjesto').value; reloadManifestacije(); });
|
||||
$('#mn-raz').addEventListener('change', ()=>{ _manifFilter.razina = $('#mn-raz').value; reloadManifestacije(); });
|
||||
$('#mn-org').addEventListener('change', ()=>{ _manifFilter.organizator = $('#mn-org').value; reloadManifestacije(); });
|
||||
$('#mn-reset').addEventListener('click', ()=>{
|
||||
_manifFilter.mjesto=''; _manifFilter.razina=''; _manifFilter.organizator=''; _manifFilter.q='';
|
||||
$('#mn-q').value=''; $('#mn-mjesto').value=''; $('#mn-raz').value=''; $('#mn-org').value='';
|
||||
reloadManifestacije();
|
||||
});
|
||||
}
|
||||
function setManifView(v){
|
||||
_state.viewManif = v;
|
||||
$('#mn-card').classList.toggle('active', v==='card');
|
||||
$('#mn-table').classList.toggle('active', v==='table');
|
||||
applyManifFilter();
|
||||
try{ localStorage.setItem('_manifViewMode', v); }catch(_){}
|
||||
if($('#mn-card')) $('#mn-card').classList.toggle('active', v==='card');
|
||||
if($('#mn-table')) $('#mn-table').classList.toggle('active', v==='table');
|
||||
renderManifBody();
|
||||
}
|
||||
function applyManifFilter(){
|
||||
const q = (($('#mn-q')?$('#mn-q').value:'') || '').toLowerCase().trim();
|
||||
const raz = $('#mn-raz') ? $('#mn-raz').value : '';
|
||||
function renderManifBody(){
|
||||
let rows = _cache.manifestacije || [];
|
||||
if(q) rows = rows.filter(m => (m.naziv||'').toLowerCase().includes(q) || (m.organizator||'').toLowerCase().includes(q) || (m.mjesto||'').toLowerCase().includes(q));
|
||||
if(raz) rows = rows.filter(m => m.razina===raz);
|
||||
if(_sort.manifestacije) rows = sortRows(rows, _sort.manifestacije.key, _sort.manifestacije.dir);
|
||||
$('#mn-cnt').textContent = rows.length+' manifestacija';
|
||||
$('#mn-out').innerHTML = _state.viewManif==='card' ? renderManifGrid(rows) : renderManifTable(rows);
|
||||
}
|
||||
// Backwards-compat: existing handlers (e.g. sortHeader) call applyManifFilter()
|
||||
function applyManifFilter(){ renderManifBody(); }
|
||||
function manifLinkFor(m){
|
||||
if(m && m.source_url) return m.source_url;
|
||||
const gq = encodeURIComponent(((m&&m.naziv)||'')+' '+((m&&m.mjesto)||'')+' sport');
|
||||
return 'https://www.google.com/search?q='+gq;
|
||||
}
|
||||
function renderManifGrid(rows){
|
||||
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
|
||||
return '<div class="grid">'+rows.map(m => `
|
||||
if(!rows.length) return '<div class="empty">Nema manifestacija za zadane filtere</div>';
|
||||
return '<div class="grid">'+rows.map(m => {
|
||||
const url = manifLinkFor(m);
|
||||
const linkIcon = '<a class="et-link" href="'+esc(url)+'" target="_blank" rel="noopener" onclick="event.stopPropagation()" title="'+(m.source_url?'Otvori izvor':'Pretraži online')+'">🔗</a>';
|
||||
return `
|
||||
<div class="entity" onclick="openManif(${m.id})">
|
||||
${m.razina?'<div class="et-tag">'+esc(m.razina)+'</div>':''}
|
||||
<div class="et">${esc(m.naziv)}</div>
|
||||
<div class="es">${txt(m.mjesto,'—')}${m.spol_kategorija?' · '+esc(m.spol_kategorija):''}</div>
|
||||
<div class="et">${esc(m.naziv)} ${linkIcon}</div>
|
||||
<div class="es">${txt(m.mjesto,'—')}${m.spol_kategorija?' · '+esc(m.spol_kategorija):''}${m.godina_od?' · od '+esc(m.godina_od):''}</div>
|
||||
<div class="em">
|
||||
${m.broj_ucesnika?'<span><b>'+esc(m.broj_ucesnika)+'</b> sudionika</span>':''}
|
||||
${m.organizator?'<span>'+esc((m.organizator||'').slice(0,40))+'</span>':''}
|
||||
</div>
|
||||
</div>`).join('')+'</div>';
|
||||
</div>`;
|
||||
}).join('')+'</div>';
|
||||
}
|
||||
function renderManifTable(rows){
|
||||
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
|
||||
if(!rows.length) return '<div class="empty">Nema manifestacija za zadane filtere</div>';
|
||||
return `<div class="card" style="padding:0;overflow-x:auto"><table>
|
||||
<thead><tr>${sortHeader('manifestacije','naziv','Naziv','')}${sortHeader('manifestacije','mjesto','Mjesto','')}${sortHeader('manifestacije','razina','Razina','')}${sortHeader('manifestacije','organizator','Organizator','')}${sortHeader('manifestacije','broj_ucesnika','Sudionici','')}<th>Link</th></tr></thead>
|
||||
<tbody>${rows.map(m => `
|
||||
<tbody>${rows.map(m => {
|
||||
const url = manifLinkFor(m);
|
||||
return `
|
||||
<tr onclick="openManif(${m.id})">
|
||||
<td><b>${esc(m.naziv)}</b></td>
|
||||
<td>${txt(m.mjesto)}</td>
|
||||
<td>${m.razina?'<span class="tag b">'+esc(m.razina)+'</span>':'—'}</td>
|
||||
<td>${txt(m.organizator)}</td>
|
||||
<td>${txt(m.broj_ucesnika)}</td>
|
||||
<td>${m.source_url?'<a href="'+esc(m.source_url)+'" target="_blank">↗</a>':'—'}</td>
|
||||
</tr>`).join('')}</tbody>
|
||||
<td><a href="${esc(url)}" target="_blank" rel="noopener" onclick="event.stopPropagation()" title="${m.source_url?'Otvori izvor':'Pretraži online'}">🔗</a></td>
|
||||
</tr>`;
|
||||
}).join('')}</tbody>
|
||||
</table></div>`;
|
||||
}
|
||||
function openManif(id){
|
||||
@@ -3787,5 +3914,6 @@ window.closePanel = function(){
|
||||
if(ov){ ov.classList.remove('open'); ov.style.removeProperty('display'); }
|
||||
};
|
||||
</script>
|
||||
<script src="/static/js/export_dropdown.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user