Files
pgz-sport/static/sport2_new.html
T

1427 lines
71 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="hr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>PGŽ SPORT — Platforma</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<style>
:root{
--pgz-blue:#003087; --pgz-blue2:#004CC4; --pgz-gold:#F4C430;
--bg0:#08090e; --bg1:#0d1021; --bg2:#111628; --bg3:#161d35; --bg4:#1c2542;
--rim:#1e2a50; --rim2:#283560;
--t0:#fff; --t1:#e2e6f0; --t2:#8a95b4; --t4:#4e5a7a;
--green:#00e88f; --red:#ff2d55; --amber:#f59e0b; --cyan:#00c8e8;
--font:'Inter',sans-serif; --mono:'JetBrains Mono',monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{font-family:var(--font);background:var(--bg0);color:var(--t1);font-size:13px;overflow-x:hidden}
a{color:var(--cyan);text-decoration:none}
a:hover{color:var(--pgz-gold)}
button,input,select{font-family:inherit;font-size:inherit;outline:none}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-track{background:var(--bg1)}
::-webkit-scrollbar-thumb{background:var(--rim2);border-radius:4px}
::-webkit-scrollbar-thumb:hover{background:var(--pgz-blue2)}
.app{display:flex;min-height:100vh}
.sb{width:240px;background:linear-gradient(180deg,var(--bg1) 0%,var(--bg0) 100%);border-right:1px solid var(--rim);position:fixed;top:0;left:0;bottom:0;display:flex;flex-direction:column;z-index:10}
.sb-h{padding:18px 18px 14px;border-bottom:1px solid var(--rim)}
.sb-h .logo{font-weight:800;font-size:14px;color:var(--t0);letter-spacing:.5px}
.sb-h .logo .g{color:var(--pgz-gold)}
.sb-h .sub{font-size:10px;color:var(--t2);margin-top:4px;text-transform:uppercase;letter-spacing:1px}
.sb-nav{flex:1;padding:10px 8px;overflow-y:auto}
.nav-i{padding:9px 12px;border-radius:6px;color:var(--t2);cursor:pointer;display:flex;align-items:center;gap:10px;font-size:12.5px;margin-bottom:2px;transition:all .15s}
.nav-i:hover{background:var(--bg2);color:var(--t1)}
.nav-i.active{background:linear-gradient(90deg,var(--pgz-blue) 0%,var(--pgz-blue2) 100%);color:#fff;font-weight:600}
.nav-i .ic{width:18px;text-align:center;font-size:14px}
.sb-foot{padding:10px 14px;border-top:1px solid var(--rim);font-size:10px;color:var(--t4)}
.main{margin-left:240px;flex:1;min-width:0}
.tb{background:var(--bg1);border-bottom:1px solid var(--rim);padding:12px 22px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:5}
.tb-t{font-size:15px;font-weight:700;color:var(--t0)}
.tb-s{font-size:11px;color:var(--t2)}
.content{padding:22px}
.section{display:none}
.section.active{display:block}
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:22px}
.kpi{background:linear-gradient(135deg,var(--bg2) 0%,var(--bg1) 100%);border:1px solid var(--rim);border-radius:8px;padding:14px 16px;position:relative;overflow:hidden}
.kpi::before{content:"";position:absolute;top:0;left:0;width:3px;height:100%;background:var(--pgz-gold)}
.kpi.b::before{background:var(--pgz-blue2)}
.kpi.g::before{background:var(--green)}
.kpi.r::before{background:var(--red)}
.kpi-l{font-size:10.5px;color:var(--t2);text-transform:uppercase;letter-spacing:1px;font-weight:600}
.kpi-v{font-size:24px;font-weight:800;color:var(--t0);margin-top:4px;font-family:var(--mono)}
.kpi-s{font-size:10px;color:var(--t4);margin-top:2px}
.card{background:var(--bg2);border:1px solid var(--rim);border-radius:8px;padding:14px;margin-bottom:14px}
.card-h{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--rim)}
.card-t{font-weight:700;color:var(--t0);font-size:13px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px}
.grid-club{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
.grid-player{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
.entity{background:var(--bg2);border:1px solid var(--rim);border-radius:8px;padding:12px;cursor:pointer;transition:all .2s;position:relative}
.entity:hover{border-color:var(--pgz-gold);background:var(--bg3);transform:translateY(-1px)}
.entity .et{font-weight:700;color:var(--t0);font-size:13px;margin-bottom:6px;line-height:1.3}
.entity .es{font-size:11px;color:var(--t2);margin-bottom:6px}
.entity .em{display:flex;gap:8px;font-size:10.5px;color:var(--t4);flex-wrap:wrap}
.entity .em b{color:var(--pgz-gold);font-weight:700}
.entity .et-tag{position:absolute;top:8px;right:8px;font-size:9px;padding:2px 6px;border-radius:3px;background:var(--pgz-blue);color:#fff;font-weight:600;text-transform:uppercase}
.player-card{background:var(--bg2);border:1px solid var(--rim);border-radius:8px;overflow:hidden;cursor:pointer;transition:all .2s}
.player-card:hover{border-color:var(--pgz-gold);transform:translateY(-1px)}
.player-card .ph{height:120px;background:linear-gradient(135deg,var(--bg3),var(--bg2));position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center}
.player-card .ph img{width:100%;height:100%;object-fit:cover;object-position:top}
.player-card .ph .no{font-size:36px;color:var(--t4);font-weight:800}
.player-card .pb{padding:10px}
.player-card .pn{font-weight:700;color:var(--t0);font-size:13px;line-height:1.2}
.player-card .pp{font-size:11px;color:var(--t2);margin-top:3px}
.player-card .pk{font-size:10.5px;color:var(--t4);margin-top:3px}
.player-card .badges{display:flex;gap:4px;margin-top:6px;flex-wrap:wrap}
.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}
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}
table td{padding:8px 10px;border-bottom:1px solid var(--rim);color:var(--t1)}
table tbody tr{cursor:pointer;transition:background .15s}
table tbody tr:hover{background:var(--bg3)}
table tbody tr.no-click{cursor:default}
table tbody tr.no-click:hover{background:transparent}
.num{font-family:var(--mono);text-align:right}
.toolbar{display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap}
.toolbar input,.toolbar select{background:var(--bg2);border:1px solid var(--rim);border-radius:5px;padding:7px 10px;color:var(--t1);font-size:12px}
.toolbar input:focus,.toolbar select:focus{border-color:var(--pgz-blue2)}
.toolbar input{min-width:200px}
.toolbar label{font-size:11px;color:var(--t2);display:flex;align-items:center;gap:6px}
.toolbar input[type=checkbox]{accent-color:var(--pgz-gold)}
.btn{background:var(--bg2);border:1px solid var(--rim);border-radius:5px;padding:7px 12px;color:var(--t1);font-size:12px;cursor:pointer;font-weight:600;transition:all .15s}
.btn:hover{background:var(--bg3);border-color:var(--rim2)}
.btn.primary{background:linear-gradient(135deg,var(--pgz-blue),var(--pgz-blue2));border-color:transparent;color:#fff}
.btn.primary:hover{filter:brightness(1.1)}
.toggle{display:inline-flex;background:var(--bg2);border:1px solid var(--rim);border-radius:5px;overflow:hidden}
.toggle button{background:transparent;border:0;padding:6px 12px;color:var(--t2);font-size:11px;font-weight:600;cursor:pointer}
.toggle button.active{background:var(--pgz-blue);color:#fff}
.tabs{display:flex;gap:4px;border-bottom:1px solid var(--rim);margin-bottom:14px;flex-wrap:wrap}
.tab{padding:8px 14px;cursor:pointer;color:var(--t2);font-weight:600;font-size:12px;border-bottom:2px solid transparent;transition:all .15s}
.tab:hover{color:var(--t1)}
.tab.active{color:var(--pgz-gold);border-color:var(--pgz-gold)}
.empty{text-align:center;padding:30px;color:var(--t4);font-size:12px;font-style:italic}
.loading{padding:24px;text-align:center;color:var(--t2);font-size:12px}
.loading::before{content:"";display:inline-block;width:12px;height:12px;border:2px solid var(--rim);border-top-color:var(--pgz-gold);border-radius:50%;animation:spin .8s linear infinite;margin-right:8px;vertical-align:middle}
@keyframes spin{to{transform:rotate(360deg)}}
.tag{display:inline-block;padding:2px 7px;font-size:10px;border-radius:3px;background:var(--bg4);color:var(--t1);font-weight:600;text-transform:uppercase;letter-spacing:.5px;margin-right:3px}
.tag.b{background:var(--pgz-blue);color:#fff}
.tag.gd{background:var(--pgz-gold);color:var(--bg0)}
.tag.gr{background:var(--green);color:var(--bg0)}
.tag.rd{background:var(--red);color:#fff}
.tag.am{background:var(--amber);color:var(--bg0)}
#panel{position:fixed;top:0;right:-620px;width:600px;max-width:96vw;height:100vh;background:var(--bg1);border-left:1px solid var(--rim);z-index:200;transition:right .25s ease;display:flex;flex-direction:column;box-shadow:-8px 0 30px rgba(0,0,0,.5)}
#panel.open{right:0}
#panel-hdr{padding:14px 16px;border-bottom:1px solid var(--rim);display:flex;align-items:center;justify-content:space-between;flex-shrink:0;background:var(--bg2)}
#panel-hdr-t{font-size:14px;font-weight:700;color:var(--t0)}
#panel-x{cursor:pointer;font-size:22px;color:var(--t4);width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:5px;transition:all .15s}
#panel-x:hover{background:var(--bg3);color:var(--red)}
#panel-body{flex:1;overflow-y:auto;padding:16px}
#panel-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:199;backdrop-filter:blur(2px)}
#panel-overlay.open{display:block}
.pp-hdr{display:flex;gap:14px;align-items:flex-start;margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid var(--rim)}
.pp-foto{width:90px;height:110px;border-radius:6px;background:var(--bg3);overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center}
.pp-foto img{width:100%;height:100%;object-fit:cover;object-position:top}
.pp-foto .no{font-size:32px;color:var(--t4);font-weight:800}
.pp-info{flex:1;min-width:0}
.pp-name{font-size:18px;font-weight:800;color:var(--t0);margin-bottom:4px}
.pp-meta{font-size:12px;color:var(--t2);margin-bottom:6px}
.pp-tags{display:flex;gap:4px;flex-wrap:wrap}
.pp-stats{display:grid;grid-template-columns:repeat(6,1fr);gap:8px;margin-bottom:14px;padding:12px;background:var(--bg2);border:1px solid var(--rim);border-radius:6px}
.pp-stat{text-align:center}
.pp-stat .v{font-size:20px;font-weight:800;color:var(--pgz-gold);font-family:var(--mono)}
.pp-stat .l{font-size:9px;color:var(--t4);text-transform:uppercase;letter-spacing:.5px;margin-top:2px;font-weight:600}
.kv{display:grid;grid-template-columns:140px 1fr;gap:6px 12px;font-size:12px}
.kv .k{color:var(--t2);font-weight:600}
.kv .v{color:var(--t1);word-break:break-word}
.utlogo{width:22px;height:22px;border-radius:50%;background:var(--bg3);object-fit:contain;vertical-align:middle;margin-right:6px}
.score{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:4px;background:var(--bg3);font-size:11px;font-weight:600}
.score.high{background:rgba(0,232,143,.15);color:var(--green)}
.score.mid{background:rgba(245,158,11,.15);color:var(--amber)}
.score.low{background:rgba(255,45,85,.15);color:var(--red)}
.row-2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media (max-width:900px){.row-2{grid-template-columns:1fr}}
.chart-box{background:var(--bg2);border:1px solid var(--rim);border-radius:8px;padding:14px;height:340px}
.chart-box canvas{max-height:300px}
.alert-card{padding:10px 12px;border-left:3px solid var(--amber);background:var(--bg2);border-radius:5px;margin-bottom:8px}
.alert-card.crit{border-color:var(--red)}
.alert-card .at{font-weight:700;font-size:12px;color:var(--t0)}
.alert-card .ad{font-size:11px;color:var(--t2);margin-top:3px}
.iframe-map{width:100%;height:140px;border:0;border-radius:5px;background:var(--bg3)}
@media (max-width:768px){
.sb{transform:translateX(-100%);transition:transform .25s}
.sb.open{transform:translateX(0)}
.main{margin-left:0}
.pp-stats{grid-template-columns:repeat(3,1fr)}
}
</style>
</head>
<body>
<div class="app">
<aside class="sb">
<div class="sb-h">
<div class="logo">PGŽ <span class="g">SPORT</span></div>
<div class="sub">Primorsko-goranska županija</div>
</div>
<nav class="sb-nav" id="nav"></nav>
<div class="sb-foot">v2.0 · 2026</div>
</aside>
<main class="main">
<div class="tb">
<div>
<div class="tb-t" id="tb-t">Dashboard</div>
<div class="tb-s" id="tb-s">Pregled stanja</div>
</div>
<div class="tb-s">
<span style="color:var(--green)"></span> API live · sport.rinet.one
</div>
</div>
<div class="content">
<section id="pg-dashboard" class="section active"></section>
<section id="pg-savezi" class="section"></section>
<section id="pg-klubovi" class="section"></section>
<section id="pg-sportasi" class="section"></section>
<section id="pg-financije" class="section"></section>
<section id="pg-objekti" class="section"></section>
<section id="pg-manifestacije" class="section"></section>
<section id="pg-forenzika" class="section"></section>
</div>
</main>
</div>
<div id="panel-overlay" onclick="closePanel()"></div>
<div id="panel">
<div id="panel-hdr">
<div id="panel-hdr-t">Detalji</div>
<div id="panel-x" onclick="closePanel()">×</div>
</div>
<div id="panel-body"></div>
</div>
<script>
//=========== CONFIG ===========
const API = '/sport/api';
const NAV_ITEMS = [
{id:'dashboard', ic:'\u{1F4CA}', label:'Dashboard'},
{id:'savezi', ic:'\u{1F3C5}', label:'Savezi'},
{id:'klubovi', ic:'⬢', label:'Klubovi'},
{id:'sportasi', ic:'\u{1F464}', label:'Sportaši'},
{id:'financije', ic:'€', label:'Financije'},
{id:'objekti', ic:'\u{1F4CD}', label:'Objekti'},
{id:'manifestacije', ic:'\u{1F4C5}', label:'Manifestacije'},
{id:'forenzika', ic:'⚠', label:'Forenzika'}
];
const SECTION_TITLES = {
dashboard: ['Dashboard', 'Pregled stanja PGŽ Sporta'],
savezi: ['Savezi', '246 sportskih saveza'],
klubovi: ['Klubovi', 'Sportski klubovi PGŽ'],
sportasi: ['Sportaši', 'Registrirani članovi'],
financije: ['Financije', 'Sufinanciranje sporta'],
objekti: ['Sportski objekti', 'Geocodirana infrastruktura'],
manifestacije: ['Manifestacije', 'Sportski događaji'],
forenzika: ['Forenzika', 'Kritični nalazi i alarmi']
};
const _cache = {savezi:null, klubovi:null, clanovi:null, objekti:null, manifestacije:null, sufin:null, dash:null};
const _state = {section:'dashboard', viewSavezi:'card', viewKlubovi:'card', viewSportasi:'card', viewObjekti:'card', viewManif:'card'};
let _proracunChart=null, _financijeChart=null;
//=========== UTIL ===========
const $ = s => document.querySelector(s);
const $$ = s => Array.from(document.querySelectorAll(s));
function esc(s){
if(s===null||s===undefined) return '';
return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
function fmtNum(n){
if(n===null||n===undefined||n==='') return '—';
const v = Number(n);
if(isNaN(v)) return esc(n);
return v.toLocaleString('hr-HR');
}
function fmtEur(n){
if(n===null||n===undefined||n==='') return '—';
const v = Number(n);
if(isNaN(v)) return esc(n);
if(v>=1000000) return '€'+(v/1000000).toFixed(2)+'M';
if(v>=1000) return '€'+(v/1000).toFixed(1)+'k';
return '€'+v.toFixed(0);
}
function fmtEurFull(n){
if(n===null||n===undefined||n==='') return '—';
const v = Number(n);
if(isNaN(v)) return esc(n);
return v.toLocaleString('hr-HR', {minimumFractionDigits:2, maximumFractionDigits:2})+' €';
}
function fmtDate(s){
if(!s) return '—';
const m = String(s).match(/^(\d{4})-(\d{2})-(\d{2})/);
if(!m) return esc(s);
return m[3]+'.'+m[2]+'.'+m[1];
}
function txt(v, fb){
if(v===null||v===undefined||v==='') return fb===undefined?'—':fb;
return esc(v);
}
async function api(path){
try{
const r = await fetch(API+path);
if(!r.ok) throw new Error('HTTP '+r.status);
return await r.json();
}catch(e){
console.error('API error', path, e);
return null;
}
}
function debounce(fn, ms){
let t;
return function(){
const args = arguments;
clearTimeout(t);
t = setTimeout(()=>fn.apply(null, args), ms);
};
}
//=========== PANEL ===========
function openPanel(title, html){
const t = $('#panel-hdr-t');
if(t) t.textContent = title || 'Detalji';
const b = $('#panel-body');
if(b) b.innerHTML = html;
$('#panel').classList.add('open');
$('#panel-overlay').classList.add('open');
}
function closePanel(){
$('#panel').classList.remove('open');
$('#panel-overlay').classList.remove('open');
}
document.addEventListener('keydown', e => { if(e.key==='Escape') closePanel(); });
//=========== NAVIGATION ===========
function buildNav(){
const nav = $('#nav');
nav.innerHTML = NAV_ITEMS.map(n => '<div class="nav-i '+(n.id===_state.section?'active':'')+'" data-id="'+n.id+'" onclick="navTo(\''+n.id+'\')"><span class="ic">'+n.ic+'</span><span>'+n.label+'</span></div>').join('');
}
function navTo(id){
_state.section = id;
$$('.nav-i').forEach(el => el.classList.toggle('active', el.dataset.id===id));
$$('.section').forEach(el => el.classList.remove('active'));
const sec = $('#pg-'+id);
if(sec) sec.classList.add('active');
const t = SECTION_TITLES[id] || [id, ''];
$('#tb-t').textContent = t[0];
$('#tb-s').textContent = t[1];
loadSection(id);
}
function loadSection(id){
switch(id){
case 'dashboard': return loadDash();
case 'savezi': return loadSavezi();
case 'klubovi': return loadKlubovi();
case 'sportasi': return loadSportasi();
case 'financije': return loadFinancije();
case 'objekti': return loadObjekti();
case 'manifestacije': return loadManifestacije();
case 'forenzika': return loadForenzika();
}
}
//=========== DASHBOARD ===========
async function loadDash(){
const root = $('#pg-dashboard');
root.innerHTML = '<div class="loading">Učitavanje dashboard podataka…</div>';
const d = await api('/dashboard');
if(!d){ root.innerHTML='<div class="empty">Greška pri dohvatu podataka</div>'; return; }
_cache.dash = d;
const proracun2026 = (d.proracun_trend||[]).filter(x=>x.godina===2026)[0];
const total2026 = proracun2026 ? proracun2026.ukupno : d.proracun_aktualni;
root.innerHTML = `
<div class="kpi-grid">
<div class="kpi"><div class="kpi-l">Saveza</div><div class="kpi-v">${fmtNum(d.aktivnih_saveza)}</div><div class="kpi-s">aktivnih</div></div>
<div class="kpi b"><div class="kpi-l">Klubova</div><div class="kpi-v">${fmtNum(d.aktivnih_klubova)}</div><div class="kpi-s">${d.nositelja_kvalitete||0} nositelja kvalitete</div></div>
<div class="kpi g"><div class="kpi-l">Sportaša</div><div class="kpi-v">${fmtNum(d.aktivnih_clanova)}</div><div class="kpi-s">${d.reprezentativaca||0} reprezentativaca</div></div>
<div class="kpi"><div class="kpi-l">Proračun ${proracun2026?proracun2026.godina:2026}</div><div class="kpi-v">${fmtEur(total2026)}</div><div class="kpi-s">javne potrebe u sportu</div></div>
<div class="kpi r"><div class="kpi-l">Kritičnih alarma</div><div class="kpi-v">${fmtNum(d.critical_alerts)}</div><div class="kpi-s">${d.warning_alerts||0} upozorenja</div></div>
</div>
<div class="row-2">
<div class="card">
<div class="card-h"><div class="card-t">📈 Proračun za sport po godinama</div><div class="tb-s">Klikni godinu za detalje</div></div>
<div class="chart-box"><canvas id="chProracun"></canvas></div>
</div>
<div class="card">
<div class="card-h"><div class="card-t">🏅 Top savezi po broju registriranih</div></div>
<div style="overflow-x:auto;max-height:340px;overflow-y:auto"><table>
<thead><tr><th>Savez</th><th class="num">Klubova</th><th class="num">Reg.</th><th class="num">Trenera</th><th class="num">Repr.</th></tr></thead>
<tbody>${(d.top_savezi||[]).slice(0,10).map(s => `
<tr class="no-click">
<td>${esc(s.naziv)}</td>
<td class="num">${fmtNum(s.klubova_clanica)}</td>
<td class="num">${fmtNum(s.registriranih)}</td>
<td class="num">${fmtNum(s.trenera)}</td>
<td class="num">${fmtNum(s.reprezentativaca)}</td>
</tr>`).join('')}</tbody>
</table></div>
</div>
</div>
<div class="card" style="margin-top:14px">
<div class="card-h"><div class="card-t">💰 Najveći primatelji 2025</div></div>
<div style="overflow-x:auto"><table>
<thead><tr><th>Klub</th><th class="num">Iznos</th></tr></thead>
<tbody>${(d.nositelji_2025||[]).slice(0,15).map(n => `
<tr class="no-click"><td>${esc(n.naziv_kluba)}</td><td class="num"><b>${fmtEurFull(n.iznos)}</b></td></tr>
`).join('')}</tbody>
</table></div>
</div>
`;
drawProracunChart(d.proracun_trend || []);
}
function drawProracunChart(trend){
const ctx = $('#chProracun');
if(!ctx) return;
if(_proracunChart){ _proracunChart.destroy(); _proracunChart=null; }
_proracunChart = new Chart(ctx, {
type:'bar',
data:{
labels: trend.map(x=>x.godina),
datasets:[{
label:'EUR',
data: trend.map(x=>x.ukupno),
backgroundColor: trend.map(x=> x.godina===2026 ? '#F4C430' : '#004CC4'),
borderRadius:4
}]
},
options:{
responsive:true, maintainAspectRatio:false,
plugins:{
legend:{display:false},
tooltip:{callbacks:{label:c => '€'+Number(c.parsed.y).toLocaleString('hr-HR')}}
},
scales:{
x:{ticks:{color:'#8a95b4'}, grid:{display:false}},
y:{ticks:{color:'#8a95b4', callback:v=>'€'+(v/1000)+'k'}, grid:{color:'#1e2a50'}}
},
onClick:(e, els) => {
if(els && els.length){
const i = els[0].index;
openProracunDrill(trend[i].godina, trend[i].ukupno);
}
}
}
});
}
async function openProracunDrill(godina, total){
openPanel('Proračun '+godina, '<div class="loading">Dohvaćanje primatelja…</div>');
const d = await api('/v2/potpore/by-year?godina='+godina);
if(!d){ openPanel('Proračun '+godina, '<div class="empty">Greška pri dohvatu</div>'); return; }
const rows = d.results || [];
const grandTotal = d.total || total || 0;
const html = `
<div class="kpi-grid" style="grid-template-columns:1fr 1fr">
<div class="kpi"><div class="kpi-l">Ukupno ${godina}</div><div class="kpi-v">${fmtEur(grandTotal)}</div></div>
<div class="kpi b"><div class="kpi-l">Primatelja</div><div class="kpi-v">${rows.length}</div></div>
</div>
<div style="overflow-x:auto"><table>
<thead><tr><th>#</th><th>Korisnik</th><th>Sport</th><th class="num">Iznos</th><th>PDF</th></tr></thead>
<tbody>
${rows.map((r,i) => `
<tr class="no-click">
<td>${i+1}</td>
<td>${esc(r.korisnik)}</td>
<td>${txt(r.sport)}</td>
<td class="num"><b>${fmtEurFull(r.iznos_eur)}</b></td>
<td>${r.source_url ? '<a href="'+esc(r.source_url)+'" target="_blank">📄</a>' : '—'}</td>
</tr>`).join('')}
</tbody>
</table></div>
`;
openPanel('Primatelji proračuna · '+godina, html);
}
//=========== SAVEZI ===========
async function loadSavezi(){
const root = $('#pg-savezi');
if(!_cache.savezi){
root.innerHTML = '<div class="loading">Učitavanje saveza…</div>';
const d = await api('/savezi?limit=250');
if(!d){ root.innerHTML='<div class="empty">Greška pri dohvatu</div>'; return; }
_cache.savezi = d.rows || [];
}
renderSaveziShell();
applySaveziFilter();
}
function renderSaveziShell(){
const root = $('#pg-savezi');
root.innerHTML = `
<div class="toolbar">
<input type="search" id="sav-q" placeholder="🔍 Pretraži savez…">
<select id="sav-pgz">
<option value="">Svi savezi</option>
<option value="1">Samo PGŽ relevantni</option>
</select>
<div class="toggle">
<button id="sav-card" class="${_state.viewSavezi==='card'?'active':''}" onclick="setSaveziView('card')">Kartice</button>
<button id="sav-table" class="${_state.viewSavezi==='table'?'active':''}" onclick="setSaveziView('table')">Tablica</button>
</div>
<span class="tb-s" id="sav-cnt"></span>
</div>
<div id="sav-out"></div>
`;
$('#sav-q').addEventListener('input', debounce(applySaveziFilter, 200));
$('#sav-pgz').addEventListener('change', applySaveziFilter);
}
function setSaveziView(v){
_state.viewSavezi = v;
$('#sav-card').classList.toggle('active', v==='card');
$('#sav-table').classList.toggle('active', v==='table');
applySaveziFilter();
}
function applySaveziFilter(){
const q = (($('#sav-q')?$('#sav-q').value:'') || '').toLowerCase().trim();
const pgz = $('#sav-pgz') ? $('#sav-pgz').value : '';
let rows = _cache.savezi || [];
if(q) rows = rows.filter(s => (s.naziv||'').toLowerCase().includes(q) || (s.sport||'').toLowerCase().includes(q));
if(pgz==='1') rows = rows.filter(s => s.pgz_relevant);
$('#sav-cnt').textContent = rows.length+' saveza';
$('#sav-out').innerHTML = _state.viewSavezi==='card' ? renderSaveziGrid(rows) : renderSaveziTable(rows);
}
function renderSaveziGrid(rows){
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
return '<div class="grid">'+rows.map(s => `
<div class="entity" onclick="openSavez(${s.id})">
${s.pgz_relevant?'<div class="et-tag">PGŽ</div>':''}
<div class="et">${esc(s.naziv)}</div>
<div class="es">${txt(s.sport,'—')} · ${txt(s.predsjednik,'bez predsjednika')}</div>
<div class="em">
<span><b>${fmtNum(s.broj_klubova)}</b> klubova</span>
<span><b>${fmtNum(s.reg_2024)}</b> reg.</span>
<span><b>${fmtNum(s.treneri_2024)}</b> trenera</span>
</div>
</div>`).join('')+'</div>';
}
function renderSaveziTable(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>Naziv</th><th>Sport</th><th>Predsjednik</th><th>Email</th><th class="num">Klubova</th><th class="num">Reg.</th><th>PGŽ</th></tr></thead>
<tbody>${rows.map(s => `
<tr onclick="openSavez(${s.id})">
<td><b>${esc(s.naziv)}</b></td>
<td>${txt(s.sport)}</td>
<td>${txt(s.predsjednik)}</td>
<td>${s.email?'<span class="tag b">'+esc(s.email)+'</span>':'—'}</td>
<td class="num">${fmtNum(s.broj_klubova)}</td>
<td class="num">${fmtNum(s.reg_2024)}</td>
<td>${s.pgz_relevant?'<span class="tag gd">PGŽ</span>':'—'}</td>
</tr>`).join('')}</tbody>
</table></div>`;
}
async function openSavez(id){
openPanel('Savez', '<div class="loading">Učitavanje saveza…</div>');
const s = await api('/savezi/'+id);
if(!s || s.detail){
openPanel('Savez', '<div class="empty">Savez nije pronađen</div>');
return;
}
const ksearch = (s.naziv||'').split(' ').slice(0,2).join(' ');
const kr = await api('/klubovi?savez='+encodeURIComponent(ksearch)+'&limit=200');
const klubovi = (kr && kr.rows) ? kr.rows.filter(k => (k.savez||'').toLowerCase().includes(ksearch.toLowerCase())) : [];
const html = `
<div class="card-h" style="border:0;padding:0;margin-bottom:14px">
<div>
<div style="font-size:18px;font-weight:800;color:var(--t0)">${esc(s.naziv)}</div>
<div style="font-size:12px;color:var(--t2);margin-top:4px">${txt(s.sport,'—')} · ${s.region||'PGŽ'}</div>
</div>
</div>
<div class="kpi-grid" style="grid-template-columns:repeat(3,1fr);margin-bottom:14px">
<div class="kpi"><div class="kpi-l">Klubova</div><div class="kpi-v">${klubovi.length}</div></div>
<div class="kpi b"><div class="kpi-l">Godina osnutka</div><div class="kpi-v" style="font-size:18px">${txt(s.godina_osnutka)}</div></div>
<div class="kpi g"><div class="kpi-l">Status</div><div class="kpi-v" style="font-size:14px">${s.aktivan?'AKTIVAN':'NEAKTIVAN'}</div></div>
</div>
<div class="card">
<div class="card-h"><div class="card-t">📋 Osnovne informacije</div></div>
<div class="kv">
<div class="k">OIB</div><div class="v">${txt(s.oib)}</div>
<div class="k">Adresa</div><div class="v">${txt(s.adresa)}</div>
<div class="k">Predsjednik</div><div class="v">${txt(s.predsjednik)}</div>
<div class="k">Tajnik</div><div class="v">${txt(s.tajnik)}</div>
<div class="k">Email</div><div class="v">${s.email?'<a href="mailto:'+esc(s.email)+'">'+esc(s.email)+'</a>':'—'}</div>
<div class="k">Telefon</div><div class="v">${txt(s.telefon)}</div>
<div class="k">Web</div><div class="v">${s.web?'<a href="'+esc(s.web)+'" target="_blank">'+esc(s.web)+'</a>':'—'}</div>
<div class="k">IBAN</div><div class="v">${txt(s.iban)}</div>
</div>
</div>
<div class="card">
<div class="card-h"><div class="card-t">⬡ Klubovi članice (${klubovi.length})</div></div>
${klubovi.length ? `<div style="overflow-x:auto;max-height:400px;overflow-y:auto"><table>
<thead><tr><th>Klub</th><th>Razina</th><th>Grad</th></tr></thead>
<tbody>${klubovi.slice(0,100).map(k => `
<tr onclick="closePanel();setTimeout(()=>openKlub(${k.id}),250)">
<td>${esc(k.klub||k.sport||'(bez naziva)')}${k.nositelj_kvalitete?' <span class="tag gd">N.K.</span>':''}</td>
<td>${txt(k.razina,'')}</td>
<td>${txt(k.grad)}</td>
</tr>`).join('')}
</tbody>
</table></div>` : '<div class="empty">Nema podataka o klubovima</div>'}
</div>
`;
openPanel('Savez · '+s.naziv, html);
}
//=========== KLUBOVI ===========
async function loadKlubovi(){
const root = $('#pg-klubovi');
if(!_cache.klubovi){
root.innerHTML = '<div class="loading">Učitavanje klubova…</div>';
const d = await api('/klubovi?limit=500');
if(!d){ root.innerHTML='<div class="empty">Greška pri dohvatu</div>'; return; }
_cache.klubovi = d.rows || [];
}
renderKluboviShell();
applyKluboviFilter();
}
function renderKluboviShell(){
const root = $('#pg-klubovi');
const sports = Array.from(new Set((_cache.klubovi||[]).map(k=>k.sport).filter(Boolean))).sort().slice(0,80);
const grads = Array.from(new Set((_cache.klubovi||[]).map(k=>k.grad).filter(Boolean))).sort();
root.innerHTML = `
<div class="toolbar">
<input type="search" id="kl-q" placeholder="🔍 Pretraži klub…">
<select id="kl-sport"><option value="">Svi sportovi</option>${sports.map(s=>'<option value="'+esc(s)+'">'+esc(s)+'</option>').join('')}</select>
<select id="kl-grad"><option value="">Svi gradovi</option>${grads.map(g=>'<option value="'+esc(g)+'">'+esc(g)+'</option>').join('')}</select>
<label><input type="checkbox" id="kl-nk"> Nositelj kvalitete</label>
<div class="toggle">
<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>
<span class="tb-s" id="kl-cnt"></span>
</div>
<div id="kl-out"></div>
`;
$('#kl-q').addEventListener('input', debounce(applyKluboviFilter, 200));
$('#kl-sport').addEventListener('change', applyKluboviFilter);
$('#kl-grad').addEventListener('change', applyKluboviFilter);
$('#kl-nk').addEventListener('change', applyKluboviFilter);
}
function setKluboviView(v){
_state.viewKlubovi = v;
$('#kl-card').classList.toggle('active', v==='card');
$('#kl-table').classList.toggle('active', v==='table');
applyKluboviFilter();
}
function applyKluboviFilter(){
const q = (($('#kl-q')?$('#kl-q').value:'') || '').toLowerCase().trim();
const sport = $('#kl-sport') ? $('#kl-sport').value : '';
const grad = $('#kl-grad') ? $('#kl-grad').value : '';
const nk = $('#kl-nk') ? $('#kl-nk').checked : false;
let rows = _cache.klubovi || [];
if(q) rows = rows.filter(k => (k.klub||'').toLowerCase().includes(q) || (k.sport||'').toLowerCase().includes(q));
if(sport) rows = rows.filter(k => k.sport===sport);
if(grad) rows = rows.filter(k => k.grad===grad);
if(nk) rows = rows.filter(k => k.nositelj_kvalitete);
$('#kl-cnt').textContent = rows.length+' klubova';
const top = rows.slice(0, 300);
$('#kl-out').innerHTML = _state.viewKlubovi==='card' ? renderKluboviGrid(top) : renderKluboviTable(top);
if(rows.length>300){
$('#kl-out').insertAdjacentHTML('beforeend', '<div class="empty">… i još '+(rows.length-300)+' klubova. Suzite filtre.</div>');
}
}
function renderKluboviGrid(rows){
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
return '<div class="grid-club">'+rows.map(k => `
<div class="entity" onclick="openKlub(${k.id})">
${k.nositelj_kvalitete?'<div class="et-tag">N.K.</div>':''}
<div class="et">${esc(k.klub||k.sport||'(bez naziva)')}</div>
<div class="es">${txt(k.razina,'')} · ${txt(k.grad,'—')}</div>
<div class="em">
<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>';
}
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>Klub</th><th>Sport</th><th>Razina</th><th>Grad</th><th class="num">Reg.</th><th class="num">Trenera</th><th>Status</th></tr></thead>
<tbody>${rows.map(k => `
<tr onclick="openKlub(${k.id})">
<td><b>${esc(k.klub||k.sport||'(bez naziva)')}</b></td>
<td>${txt(k.sport)}</td>
<td>${txt(k.razina)}</td>
<td>${txt(k.grad)}</td>
<td class="num">${fmtNum(k.registriranih)}</td>
<td class="num">${fmtNum(k.trenera)}</td>
<td>${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>`;
}
async function openKlub(id){
openPanel('Klub', '<div class="loading">Učitavanje kluba…</div>');
const k = await api('/klubovi/'+id);
if(!k || k.detail){
openPanel('Klub', '<div class="empty">Klub nije pronađen</div>');
return;
}
const stats = k.stats || {};
const clanovi = k.clanovi || [];
const potpore = k.potpore || [];
let scoreCount = 0;
if(k.oib) scoreCount++;
if(k.predsjednik) scoreCount++;
if(k.tajnik) scoreCount++;
if(k.email) scoreCount++;
if(k.web || k.web_stranica) scoreCount++;
if(k.sjediste || k.adresa) scoreCount++;
if(k.ciljevi) scoreCount++;
if(k.opis_djelatnosti) scoreCount++;
const scoreClass = scoreCount>=6?'high':(scoreCount>=4?'mid':'low');
const html = `
<div class="card-h" style="border:0;padding:0;margin-bottom:14px">
<div>
<div style="font-size:18px;font-weight:800;color:var(--t0)">${esc(k.naziv||'(bez naziva)')}</div>
<div style="font-size:12px;color:var(--t2);margin-top:4px">${txt(k.sport,'—')} · ${txt(k.razina,'')} · ${txt(k.grad,'')}</div>
</div>
</div>
<div class="kpi-grid" style="grid-template-columns:repeat(4,1fr);margin-bottom:14px">
<div class="kpi"><div class="kpi-l">Sportaša</div><div class="kpi-v">${fmtNum(stats.broj_clanova||clanovi.length)}</div></div>
<div class="kpi b"><div class="kpi-l">Registriranih</div><div class="kpi-v">${fmtNum(stats.broj_registriranih)}</div></div>
<div class="kpi g"><div class="kpi-l">Trenera</div><div class="kpi-v">${fmtNum(stats.broj_trenera)}</div></div>
<div class="kpi"><div class="kpi-l">Score baze</div><div class="kpi-v" style="font-size:18px"><span class="score ${scoreClass}">${scoreCount}/8</span></div></div>
</div>
<div class="tabs">
<div class="tab active" onclick="switchKlubTab(this,'k-info')">Info</div>
<div class="tab" onclick="switchKlubTab(this,'k-clan')">Sportaši (${clanovi.length})</div>
<div class="tab" onclick="switchKlubTab(this,'k-pot')">Potpore (${potpore.length})</div>
</div>
<div id="k-info" class="ktab">
<div class="kv">
<div class="k">Naziv</div><div class="v">${esc(k.naziv||'')}</div>
<div class="k">OIB</div><div class="v">${txt(k.oib)}</div>
<div class="k">Sport</div><div class="v">${txt(k.sport)}</div>
<div class="k">Razina</div><div class="v">${txt(k.razina)}</div>
<div class="k">Savez</div><div class="v">${txt(k.savez_naziv)}</div>
<div class="k">Predsjednik</div><div class="v">${txt(k.predsjednik)}</div>
<div class="k">Tajnik</div><div class="v">${txt(k.tajnik)}</div>
<div class="k">Trener</div><div class="v">${txt(k.trener_glavni)}</div>
<div class="k">Sjedište</div><div class="v">${txt(k.sjediste||k.adresa)}</div>
<div class="k">Grad</div><div class="v">${txt(k.grad)}</div>
<div class="k">Email</div><div class="v">${k.email?'<a href="mailto:'+esc(k.email)+'">'+esc(k.email)+'</a>':'—'}</div>
<div class="k">Telefon</div><div class="v">${txt(k.telefon)}</div>
<div class="k">Web</div><div class="v">${(k.web||k.web_stranica)?'<a href="'+esc(k.web||k.web_stranica)+'" target="_blank">'+esc(k.web||k.web_stranica)+'</a>':'—'}</div>
<div class="k">Osnovan</div><div class="v">${txt(k.godina_osnutka)}</div>
<div class="k">Nositelj kvalitete</div><div class="v">${k.nositelj_kvalitete?'<span class="tag gd">DA</span>':'<span class="tag">NE</span>'}</div>
</div>
${k.napomena ? '<div class="card" style="margin-top:14px"><div class="card-t" style="margin-bottom:6px">Napomena</div><div style="font-size:12px;color:var(--t1);line-height:1.5">'+esc(k.napomena)+'</div></div>' : ''}
</div>
<div id="k-clan" class="ktab" style="display:none">
${clanovi.length ? `<div style="overflow-x:auto;max-height:500px;overflow-y:auto"><table>
<thead><tr><th>Sportaš</th><th>Spol</th><th>Pozicija</th><th>Tagovi</th></tr></thead>
<tbody>${clanovi.map(c => `
<tr onclick="closePanel();setTimeout(()=>openSportas(${c.id}),250)">
<td><b>${esc(c.ime||'')} ${esc(c.prezime||'')}</b></td>
<td>${txt(c.spol)}</td>
<td>${txt(c.pozicija)}</td>
<td>${c.reprezentativac?'<span class="tag gd">REPR</span>':''}${c.kategoriziran?'<span class="tag b">KAT</span>':''}${c.stipendiran?'<span class="tag gr">STIP</span>':''}</td>
</tr>`).join('')}
</tbody>
</table></div>` : '<div class="empty">Nema podataka o sportašima</div>'}
</div>
<div id="k-pot" class="ktab" style="display:none">
${potpore.length ? `<div style="overflow-x:auto"><table>
<thead><tr><th>Godina</th><th>Naziv</th><th class="num">Iznos</th></tr></thead>
<tbody>${potpore.map(p => `
<tr class="no-click">
<td>${esc(p.godina)}</td>
<td>${esc(p.naziv_kluba||'')}</td>
<td class="num"><b>${fmtEurFull(p.iznos)}</b></td>
</tr>`).join('')}
</tbody>
</table></div>` : '<div class="empty">Nema zabilježenih potpora</div>'}
</div>
`;
openPanel('Klub · '+(k.naziv||''), html);
}
function switchKlubTab(el, tabId){
const parent = el.parentElement;
parent.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
el.classList.add('active');
parent.parentElement.querySelectorAll('.ktab').forEach(t=>t.style.display='none');
const target = document.getElementById(tabId);
if(target) target.style.display='block';
}
//=========== SPORTAŠI ===========
async function loadSportasi(){
const root = $('#pg-sportasi');
if(!_cache.clanovi){
root.innerHTML = '<div class="loading">Učitavanje sportaša…</div>';
const d = await api('/clanovi-full?limit=500');
if(!d){ root.innerHTML='<div class="empty">Greška pri dohvatu</div>'; return; }
_cache.clanovi = d.rows || [];
}
renderSportasiShell();
applySportasiFilter();
}
function renderSportasiShell(){
const root = $('#pg-sportasi');
root.innerHTML = `
<div class="toolbar">
<input type="search" id="sp-q" placeholder="🔍 Ime ili prezime…">
<select id="sp-hoo">
<option value="">Sve HOO kategorije</option>
<option value="1">I. kategorija</option>
<option value="2">II. kategorija</option>
<option value="3">III. kategorija</option>
<option value="4">IV. kategorija</option>
<option value="5">V. kategorija</option>
</select>
<label><input type="checkbox" id="sp-rep"> Samo reprezentativci</label>
<label><input type="checkbox" id="sp-foto"> Samo s fotografijom</label>
<div class="toggle">
<button id="sp-card" class="${_state.viewSportasi==='card'?'active':''}" onclick="setSportasiView('card')">Kartice</button>
<button id="sp-table" class="${_state.viewSportasi==='table'?'active':''}" onclick="setSportasiView('table')">Tablica</button>
</div>
<span class="tb-s" id="sp-cnt"></span>
<button class="btn primary" onclick="openSportas(449)">⭐ Test: Josip Zec</button>
</div>
<div id="sp-out"></div>
`;
$('#sp-q').addEventListener('input', debounce(applySportasiFilter, 200));
$('#sp-hoo').addEventListener('change', applySportasiFilter);
$('#sp-rep').addEventListener('change', applySportasiFilter);
$('#sp-foto').addEventListener('change', applySportasiFilter);
}
function setSportasiView(v){
_state.viewSportasi = v;
$('#sp-card').classList.toggle('active', v==='card');
$('#sp-table').classList.toggle('active', v==='table');
applySportasiFilter();
}
function applySportasiFilter(){
const q = (($('#sp-q')?$('#sp-q').value:'') || '').toLowerCase().trim();
const hoo = $('#sp-hoo') ? $('#sp-hoo').value : '';
const rep = $('#sp-rep') ? $('#sp-rep').checked : false;
const foto = $('#sp-foto') ? $('#sp-foto').checked : false;
let rows = _cache.clanovi || [];
if(q) rows = rows.filter(c => ((c.ime||'')+' '+(c.prezime||'')).toLowerCase().includes(q));
if(rep) rows = rows.filter(c => c.reprezentativac);
if(foto) rows = rows.filter(c => c.slika_url);
if(hoo) rows = rows.filter(c => String(c.hoo_kategorija||c.kategorija_hoo||'')===hoo);
$('#sp-cnt').textContent = rows.length+' sportaša';
const top = rows.slice(0, 300);
$('#sp-out').innerHTML = _state.viewSportasi==='card' ? renderSportasiGrid(top) : renderSportasiTable(top);
if(rows.length>300){
$('#sp-out').insertAdjacentHTML('beforeend', '<div class="empty">… i još '+(rows.length-300)+' sportaša. Suzite filtre.</div>');
}
}
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>';
}
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 hooCat = c.hoo_kategorija || c.kategorija_hoo;
return `
<div class="player-card" onclick="openSportas(${c.id})">
<div class="ph">${photo}</div>
<div class="pb">
<div class="pn">${esc(c.ime||'')} ${esc(c.prezime||'')}</div>
<div class="pp">${txt(c.sport,'—')} · ${txt(c.pozicija,'')}</div>
<div class="pk">${txt(c.klub_naziv_godisnjak,'')}</div>
<div class="badges">
${c.reprezentativac?'<span class="badge repr">REPR</span>':''}
${hooCat?'<span class="badge hoo">HOO '+esc(hooCat)+'</span>':''}
${c.stipendiran?'<span class="badge">STIP</span>':''}
${c.broj_dresa?'<span class="badge">#'+esc(c.broj_dresa)+'</span>':''}
</div>
</div>
</div>`;
}
function renderSportasiTable(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>Sportaš</th><th>Sport</th><th>Pozicija</th><th>HOO</th><th>Repr.</th><th>Foto</th></tr></thead>
<tbody>${rows.map(c => `
<tr onclick="openSportas(${c.id})">
<td><b>${esc(c.ime||'')} ${esc(c.prezime||'')}</b></td>
<td>${txt(c.sport)}</td>
<td>${txt(c.pozicija)}</td>
<td>${(c.hoo_kategorija||c.kategorija_hoo)?'<span class="tag b">'+esc(c.hoo_kategorija||c.kategorija_hoo)+'</span>':'—'}</td>
<td>${c.reprezentativac?'<span class="tag gd">DA</span>':'—'}</td>
<td>${c.slika_url?'📸':'—'}</td>
</tr>`).join('')}</tbody>
</table></div>`;
}
async function openSportas(id){
openPanel('Sportaš', '<div class="loading">Učitavanje profila…</div>');
const d = await api('/sportas/'+id+'/profil');
if(!d || d.detail){
openPanel('Sportaš', '<div class="empty">Sportaš nije pronađen</div>');
return;
}
const stats = d.stats || {};
const sezone = d.clan_sezona || [];
const utakmice = d.utakmice || [];
const nagrade = d.nagrade || [];
const godisnjaci = d.godisnjak_godine || d.godisnjaci || [];
const initials = (((d.ime||'?')[0]||'?')+((d.prezime||'?')[0]||'?')).toUpperCase();
const photo = d.slika_url ? '<img src="'+esc(d.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 dob = d.datum_rodjenja || d.datum_rodenja;
const hooCat = d.hoo_kategorija || d.kategorija_hoo;
const html = `
<div class="pp-hdr">
<div class="pp-foto">${photo}</div>
<div class="pp-info">
<div class="pp-name">${esc(d.ime||'')} ${esc(d.prezime||'')}</div>
<div class="pp-meta">${txt(d.sport,'—')} · ${txt(d.pozicija,'')} · <b>${esc(d.klub_naziv_full||d.klub_naziv_godisnjak||'—')}</b></div>
<div class="pp-meta">📅 ${fmtDate(dob)}${(d.mjesto_rodjenja||d.mjesto_rodenja)?' · '+esc(d.mjesto_rodjenja||d.mjesto_rodenja):''}</div>
<div class="pp-tags">
${d.aktivan?'<span class="tag gr">AKTIVAN</span>':'<span class="tag rd">NEAKTIVAN</span>'}
${d.reprezentativac?'<span class="tag gd">REPR</span>':''}
${hooCat?'<span class="tag b">HOO '+esc(hooCat)+'</span>':''}
${d.broj_dresa?'<span class="tag">#'+esc(d.broj_dresa)+'</span>':''}
${d.stipendiran?'<span class="tag am">STIP</span>':''}
</div>
</div>
</div>
<div class="pp-stats">
<div class="pp-stat"><div class="v">${fmtNum(stats.ukupno_nastupa||0)}</div><div class="l">Nastupi</div></div>
<div class="pp-stat"><div class="v">${fmtNum(stats.ukupno_pogodaka||0)}</div><div class="l">Golovi</div></div>
<div class="pp-stat"><div class="v">${fmtNum(stats.ukupno_asistencija||0)}</div><div class="l">Asist.</div></div>
<div class="pp-stat"><div class="v" style="color:var(--amber)">${fmtNum(stats.ukupno_zutih||0)}</div><div class="l">Žuti</div></div>
<div class="pp-stat"><div class="v" style="color:var(--red)">${fmtNum(stats.ukupno_crvenih||0)}</div><div class="l">Crveni</div></div>
<div class="pp-stat"><div class="v">${fmtNum(stats.sezone_aktivne||sezone.length)}</div><div class="l">Sezona</div></div>
</div>
<div class="tabs">
<div class="tab active" onclick="switchPlayerTab(this,'p-sez')">Sezone (${sezone.length})</div>
<div class="tab" onclick="switchPlayerTab(this,'p-utak')">Utakmice (${utakmice.length})</div>
<div class="tab" onclick="switchPlayerTab(this,'p-bio')">Bio</div>
<div class="tab" onclick="switchPlayerTab(this,'p-god')">Godišnjaci (${godisnjaci.length})</div>
<div class="tab" onclick="switchPlayerTab(this,'p-nag')">Nagrade (${nagrade.length})</div>
</div>
<div id="p-sez" class="ptab">
${sezone.length ? `<div style="overflow-x:auto"><table>
<thead><tr><th>Sezona</th><th>Natjecanje</th><th>Klub</th><th class="num">Nas.</th><th class="num">Gol.</th><th class="num">Asis.</th><th class="num">Žuti</th><th class="num">Crv.</th><th></th></tr></thead>
<tbody>${sezone.map(s => `
<tr class="no-click">
<td><b>${esc(s.sezona||'')}</b></td>
<td>${esc(s.natjecanje||'')}</td>
<td>${esc(s.klub_naziv||'')}</td>
<td class="num">${fmtNum(s.nastupi)}</td>
<td class="num"><b style="color:var(--pgz-gold)">${fmtNum(s.pogoci)}</b></td>
<td class="num">${fmtNum(s.asistencije)}</td>
<td class="num">${fmtNum(s.zuti_kartoni)}</td>
<td class="num">${fmtNum(s.crveni_kartoni)}</td>
<td>${s.natjecanje_url?'<a href="'+esc(s.natjecanje_url)+'" target="_blank">↗</a>':''}</td>
</tr>`).join('')}
</tbody>
</table></div>` : '<div class="empty">Nema sezonskih podataka</div>'}
</div>
<div id="p-utak" class="ptab" style="display:none">
${utakmice.length ? `<div style="overflow-x:auto;max-height:500px;overflow-y:auto"><table>
<thead><tr><th>Datum</th><th>Natjecanje</th><th colspan="3">Susret</th><th class="num">Gol.</th><th class="num">Min.</th><th></th></tr></thead>
<tbody>${utakmice.map(u => `
<tr class="no-click">
<td>${fmtDate(u.datum)}</td>
<td>${esc(u.natjecanje||'')}</td>
<td>${u.klub_dom_logo?'<img src="'+esc(u.klub_dom_logo)+'" class="utlogo" onerror="this.style.display=\'none\'">':''}${esc(u.klub_dom||'')}</td>
<td><b>${esc(u.rezultat||'-')}</b></td>
<td>${u.klub_gost_logo?'<img src="'+esc(u.klub_gost_logo)+'" class="utlogo" onerror="this.style.display=\'none\'">':''}${esc(u.klub_gost||'')}</td>
<td class="num"><b style="color:var(--pgz-gold)">${fmtNum(u.pogodaka)}</b></td>
<td class="num">${fmtNum(u.minute)}</td>
<td>${u.source_url?'<a href="'+esc(u.source_url)+'" target="_blank">↗</a>':''}</td>
</tr>`).join('')}
</tbody>
</table></div>` : '<div class="empty">Nema podataka o utakmicama</div>'}
</div>
<div id="p-bio" class="ptab" style="display:none">
<div class="kv">
<div class="k">OIB</div><div class="v">${txt(d.oib)}</div>
<div class="k">Datum rođenja</div><div class="v">${fmtDate(dob)}</div>
<div class="k">Mjesto rođenja</div><div class="v">${txt(d.mjesto_rodjenja||d.mjesto_rodenja)}</div>
<div class="k">Spol</div><div class="v">${txt(d.spol)}</div>
<div class="k">Visina</div><div class="v">${d.visina_cm?d.visina_cm+' cm':'—'}</div>
<div class="k">Težina</div><div class="v">${d.tezina_kg?d.tezina_kg+' kg':'—'}</div>
<div class="k">Dom. noga</div><div class="v">${txt(d.dominantna_noga)}</div>
<div class="k">Status</div><div class="v">${d.aktivan?'AKTIVAN':'NEAKTIVAN'}</div>
<div class="k">Datum pristupa</div><div class="v">${fmtDate(d.datum_pristupa)}</div>
<div class="k">Email</div><div class="v">${d.email?'<a href="mailto:'+esc(d.email)+'">'+esc(d.email)+'</a>':'—'}</div>
<div class="k">Telefon</div><div class="v">${txt(d.telefon)}</div>
<div class="k">Profil</div><div class="v">${(d.profile_url||d.scrape_url)?'<a href="'+esc(d.profile_url||d.scrape_url)+'" target="_blank">↗ vanjski profil</a>':'—'}</div>
</div>
${d.biografija ? '<div class="card" style="margin-top:14px"><div class="card-t">Biografija</div><div style="font-size:12px;line-height:1.5;color:var(--t1);margin-top:6px">'+esc(d.biografija)+'</div></div>' : ''}
</div>
<div id="p-god" class="ptab" style="display:none">
${godisnjaci.length ? `<div class="kv">
<div class="k">Prvi godišnjak</div><div class="v">${esc(d.godisnjak_prvi||godisnjaci[0])}</div>
<div class="k">Zadnji godišnjak</div><div class="v">${esc(d.godisnjak_zadnji||godisnjaci[godisnjaci.length-1])}</div>
<div class="k">Sve godine</div><div class="v">${godisnjaci.map(g=>'<span class="tag b">'+esc(g)+'</span>').join(' ')}</div>
</div>` : '<div class="empty">Nema podataka o godišnjacima</div>'}
</div>
<div id="p-nag" class="ptab" style="display:none">
${nagrade.length ? `<div style="overflow-x:auto"><table>
<thead><tr><th>Godina</th><th>Nagrada</th><th>Razina</th></tr></thead>
<tbody>${nagrade.map(n => `
<tr class="no-click">
<td>${esc(n.godina||'')}</td>
<td>${esc(n.naziv||n.nagrada||'')}</td>
<td>${esc(n.razina||'')}</td>
</tr>`).join('')}
</tbody>
</table></div>` : '<div class="empty">Nema zabilježenih nagrada</div>'}
</div>
`;
openPanel('Sportaš · '+(d.ime||'')+' '+(d.prezime||''), html);
}
function switchPlayerTab(el, tabId){
const parent = el.parentElement;
parent.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
el.classList.add('active');
parent.parentElement.querySelectorAll('.ptab').forEach(t=>t.style.display='none');
const target = document.getElementById(tabId);
if(target) target.style.display='block';
}
//=========== FINANCIJE ===========
async function loadFinancije(){
const root = $('#pg-financije');
root.innerHTML = `
<div class="toolbar">
<select id="fi-god">
<option value="2026">2026</option>
<option value="2025">2025</option>
<option value="2024">2024</option>
</select>
<input type="search" id="fi-q" placeholder="🔍 Pretraži korisnika ili sport…">
<span class="tb-s" id="fi-cnt"></span>
</div>
<div id="fi-kpi"></div>
<div class="row-2" style="margin-top:14px">
<div class="card">
<div class="card-h"><div class="card-t">📊 Raspodjela po sportovima</div></div>
<div class="chart-box"><canvas id="chSport"></canvas></div>
</div>
<div class="card" id="fi-top"></div>
</div>
<div class="card" style="margin-top:14px">
<div class="card-h"><div class="card-t">📋 Svi primatelji</div></div>
<div id="fi-table"></div>
</div>
`;
$('#fi-god').addEventListener('change', refreshFinancije);
$('#fi-q').addEventListener('input', debounce(refreshFinancije, 200));
refreshFinancije();
}
async function refreshFinancije(){
const god = $('#fi-god').value;
const q = ($('#fi-q').value || '').toLowerCase().trim();
const [analytics, byyear] = await Promise.all([
api('/v2/analytics/proracun-sport?godina='+god),
api('/v2/potpore/by-year?godina='+god)
]);
const total = (analytics && analytics.total) || (byyear && byyear.total) || 0;
const poSportu = (analytics && analytics.po_sportu) || [];
let rows = (byyear && byyear.results) || [];
if(q) rows = rows.filter(r => (r.korisnik||'').toLowerCase().includes(q) || (r.sport||'').toLowerCase().includes(q));
$('#fi-kpi').innerHTML = `
<div class="kpi-grid">
<div class="kpi"><div class="kpi-l">Ukupno ${god}</div><div class="kpi-v">${fmtEur(total)}</div></div>
<div class="kpi b"><div class="kpi-l">Primatelja</div><div class="kpi-v">${rows.length}</div></div>
<div class="kpi g"><div class="kpi-l">Sportova</div><div class="kpi-v">${poSportu.filter(x=>x.sport).length}</div></div>
<div class="kpi"><div class="kpi-l">Najveća stavka</div><div class="kpi-v" style="font-size:18px">${rows.length?fmtEur(Math.max.apply(null, rows.map(r=>Number(r.iznos_eur||0)))):'—'}</div></div>
</div>
`;
$('#fi-cnt').textContent = rows.length+' primatelja';
const top10 = rows.slice().sort((a,b)=>Number(b.iznos_eur||0)-Number(a.iznos_eur||0)).slice(0,10);
$('#fi-top').innerHTML = `
<div class="card-h"><div class="card-t">🏆 Top 10 primatelja</div></div>
<div style="overflow-x:auto;max-height:340px;overflow-y:auto"><table>
<thead><tr><th>#</th><th>Korisnik</th><th class="num">Iznos</th></tr></thead>
<tbody>${top10.map((r,i) => `
<tr class="no-click">
<td>${i+1}</td>
<td>${esc(r.korisnik)}</td>
<td class="num"><b>${fmtEur(r.iznos_eur)}</b></td>
</tr>`).join('')}</tbody>
</table></div>
`;
const sportData = poSportu.filter(x=>x.sport).slice(0, 12);
drawFinancijeChart(sportData);
$('#fi-table').innerHTML = `<div style="overflow-x:auto"><table>
<thead><tr><th>#</th><th>Korisnik</th><th>Sport</th><th>Vrsta</th><th class="num">Iznos</th><th>Izvor</th><th>PDF</th></tr></thead>
<tbody>${rows.map((r,i) => `
<tr class="no-click">
<td>${i+1}</td>
<td>${esc(r.korisnik)}</td>
<td>${txt(r.sport)}</td>
<td>${txt(r.vrsta)}</td>
<td class="num"><b>${fmtEurFull(r.iznos_eur)}</b></td>
<td>${txt(r.izvor)}</td>
<td>${r.source_url?'<a href="'+esc(r.source_url)+'" target="_blank">📄</a>':'—'}</td>
</tr>`).join('')}
</tbody>
</table></div>`;
}
function drawFinancijeChart(data){
const ctx = $('#chSport');
if(!ctx) return;
if(_financijeChart){ _financijeChart.destroy(); _financijeChart=null; }
const colors = ['#003087','#004CC4','#F4C430','#00e88f','#00c8e8','#ff2d55','#f59e0b','#a855f7','#ec4899','#14b8a6','#84cc16','#f97316'];
_financijeChart = new Chart(ctx, {
type:'doughnut',
data:{
labels: data.map(x=>x.sport),
datasets:[{
data: data.map(x=>x.ukupno),
backgroundColor: colors.slice(0, data.length),
borderColor: '#0d1021',
borderWidth:2
}]
},
options:{
responsive:true, maintainAspectRatio:false,
plugins:{
legend:{position:'right', labels:{color:'#e2e6f0', font:{size:10}, boxWidth:10}},
tooltip:{callbacks:{label:c => c.label+': €'+Number(c.parsed).toLocaleString('hr-HR')}}
}
}
});
}
//=========== OBJEKTI ===========
async function loadObjekti(){
const root = $('#pg-objekti');
if(!_cache.objekti){
root.innerHTML = '<div class="loading">Učitavanje objekata…</div>';
const d = await api('/sportski-objekti');
if(!d){ root.innerHTML='<div class="empty">Greška pri dohvatu</div>'; return; }
_cache.objekti = d.rows || (Array.isArray(d) ? d : []);
}
renderObjektiShell();
applyObjektiFilter();
}
function renderObjektiShell(){
const root = $('#pg-objekti');
const tipovi = Array.from(new Set((_cache.objekti||[]).map(o=>o.tip).filter(Boolean))).sort();
const grads = Array.from(new Set((_cache.objekti||[]).map(o=>o.grad).filter(Boolean))).sort();
root.innerHTML = `
<div class="toolbar">
<input type="search" id="ob-q" placeholder="🔍 Pretraži objekt…">
<select id="ob-tip"><option value="">Svi tipovi</option>${tipovi.map(t=>'<option value="'+esc(t)+'">'+esc(t)+'</option>').join('')}</select>
<select id="ob-grad"><option value="">Svi gradovi</option>${grads.map(g=>'<option value="'+esc(g)+'">'+esc(g)+'</option>').join('')}</select>
<label><input type="checkbox" id="ob-geo"> Samo s koordinatama</label>
<div class="toggle">
<button id="ob-card" class="${_state.viewObjekti==='card'?'active':''}" onclick="setObjektiView('card')">Kartice</button>
<button id="ob-table" class="${_state.viewObjekti==='table'?'active':''}" onclick="setObjektiView('table')">Tablica</button>
</div>
<span class="tb-s" id="ob-cnt"></span>
</div>
<div id="ob-out"></div>
`;
$('#ob-q').addEventListener('input', debounce(applyObjektiFilter, 200));
$('#ob-tip').addEventListener('change', applyObjektiFilter);
$('#ob-grad').addEventListener('change', applyObjektiFilter);
$('#ob-geo').addEventListener('change', applyObjektiFilter);
}
function setObjektiView(v){
_state.viewObjekti = v;
$('#ob-card').classList.toggle('active', v==='card');
$('#ob-table').classList.toggle('active', v==='table');
applyObjektiFilter();
}
function applyObjektiFilter(){
const q = (($('#ob-q')?$('#ob-q').value:'') || '').toLowerCase().trim();
const tip = $('#ob-tip') ? $('#ob-tip').value : '';
const grad = $('#ob-grad') ? $('#ob-grad').value : '';
const geo = $('#ob-geo') ? $('#ob-geo').checked : false;
let rows = _cache.objekti || [];
if(q) rows = rows.filter(o => (o.naziv||'').toLowerCase().includes(q) || (o.upravitelj||'').toLowerCase().includes(q));
if(tip) rows = rows.filter(o => o.tip===tip);
if(grad) rows = rows.filter(o => o.grad===grad);
if(geo) rows = rows.filter(o => o.lat && o.lng);
$('#ob-cnt').textContent = rows.length+' objekata';
$('#ob-out').innerHTML = _state.viewObjekti==='card' ? renderObjektiGrid(rows) : renderObjektiTable(rows);
}
function renderObjektiGrid(rows){
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
return '<div class="grid">'+rows.map(o => `
<div class="entity" onclick="openObjekt(${o.id})">
${o.lat&&o.lng?'<div class="et-tag">📍 GEO</div>':''}
<div class="et">${esc(o.naziv)}</div>
<div class="es">${txt(o.tip,'—')} · ${txt(o.grad,'—')}</div>
<div class="em">
${o.upravitelj?'<span>'+esc(o.upravitelj)+'</span>':''}
${o.kapacitet?'<span><b>'+fmtNum(o.kapacitet)+'</b> mjesta</span>':''}
${o.izgradeno?'<span>est. <b>'+esc(o.izgradeno)+'</b></span>':''}
</div>
</div>`).join('')+'</div>';
}
function renderObjektiTable(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>Naziv</th><th>Tip</th><th>Grad</th><th>Upravitelj</th><th class="num">Izgrađeno</th><th>GPS</th></tr></thead>
<tbody>${rows.map(o => `
<tr onclick="openObjekt(${o.id})">
<td><b>${esc(o.naziv)}</b></td>
<td>${txt(o.tip)}</td>
<td>${txt(o.grad)}</td>
<td>${txt(o.upravitelj)}</td>
<td class="num">${txt(o.izgradeno)}</td>
<td>${o.lat&&o.lng?'<span class="tag gr">📍</span>':'—'}</td>
</tr>`).join('')}</tbody>
</table></div>`;
}
function openObjekt(id){
const o = (_cache.objekti||[]).find(x => x.id===id);
if(!o){ openPanel('Objekt', '<div class="empty">Objekt nije pronađen</div>'); return; }
const mapUrl = (o.lat && o.lng) ? 'https://maps.google.com/maps?q='+o.lat+','+o.lng+'&z=17' : null;
const embedUrl = (o.lat && o.lng) ? 'https://maps.google.com/maps?q='+o.lat+','+o.lng+'&z=15&output=embed' : null;
const html = `
<div class="card-h" style="border:0;padding:0;margin-bottom:14px">
<div>
<div style="font-size:18px;font-weight:800;color:var(--t0)">${esc(o.naziv)}</div>
<div style="font-size:12px;color:var(--t2);margin-top:4px">${txt(o.tip,'—')} · ${txt(o.grad,'')}</div>
</div>
</div>
${embedUrl?'<iframe class="iframe-map" style="height:240px" src="'+embedUrl+'" loading="lazy"></iframe>':''}
<div class="card" style="margin-top:14px">
<div class="card-h"><div class="card-t">📋 Detalji</div></div>
<div class="kv">
<div class="k">Tip</div><div class="v">${txt(o.tip)}</div>
<div class="k">Adresa</div><div class="v">${txt(o.adresa)}</div>
<div class="k">Grad</div><div class="v">${txt(o.grad)}</div>
<div class="k">Upravitelj</div><div class="v">${txt(o.upravitelj)}</div>
<div class="k">OIB</div><div class="v">${txt(o.upravitelj_oib)}</div>
<div class="k">Kapacitet</div><div class="v">${o.kapacitet?fmtNum(o.kapacitet)+' mjesta':'—'}</div>
<div class="k">Veličina</div><div class="v">${txt(o.veličina)}</div>
<div class="k">Sportovi</div><div class="v">${(o.sportovi||[]).map(s=>'<span class="tag b">'+esc(s)+'</span>').join(' ')||'—'}</div>
<div class="k">Izgrađeno</div><div class="v">${txt(o.izgradeno)}</div>
<div class="k">Obnovljeno</div><div class="v">${txt(o.obnovljeno_god)}</div>
<div class="k">Natkriven</div><div class="v">${o.natkrita?'DA':'NE'}</div>
<div class="k">Web</div><div class="v">${o.web?'<a href="'+esc(o.web)+'" target="_blank">'+esc(o.web)+'</a>':'—'}</div>
<div class="k">Koordinate</div><div class="v">${(o.lat&&o.lng)?'<a href="'+mapUrl+'" target="_blank">'+o.lat.toFixed(5)+', '+o.lng.toFixed(5)+' ↗</a>':'—'}</div>
</div>
</div>
`;
openPanel('Objekt · '+o.naziv, html);
}
//=========== MANIFESTACIJE ===========
async function loadManifestacije(){
const root = $('#pg-manifestacije');
if(!_cache.manifestacije){
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 : []);
}
renderManifShell();
applyManifFilter();
}
function renderManifShell(){
const root = $('#pg-manifestacije');
const razine = Array.from(new Set((_cache.manifestacije||[]).map(m=>m.razina).filter(Boolean))).sort();
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>
<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>
</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);
}
function setManifView(v){
_state.viewManif = v;
$('#mn-card').classList.toggle('active', v==='card');
$('#mn-table').classList.toggle('active', v==='table');
applyManifFilter();
}
function applyManifFilter(){
const q = (($('#mn-q')?$('#mn-q').value:'') || '').toLowerCase().trim();
const raz = $('#mn-raz') ? $('#mn-raz').value : '';
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);
$('#mn-cnt').textContent = rows.length+' manifestacija';
$('#mn-out').innerHTML = _state.viewManif==='card' ? renderManifGrid(rows) : renderManifTable(rows);
}
function renderManifGrid(rows){
if(!rows.length) return '<div class="empty">Nema rezultata</div>';
return '<div class="grid">'+rows.map(m => `
<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="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>';
}
function renderManifTable(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>Naziv</th><th>Mjesto</th><th>Razina</th><th>Organizator</th><th>Sudionici</th><th>Link</th></tr></thead>
<tbody>${rows.map(m => `
<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>
</table></div>`;
}
function openManif(id){
const m = (_cache.manifestacije||[]).find(x => x.id===id);
if(!m){ openPanel('Manifestacija', '<div class="empty">Nije pronađeno</div>'); return; }
const html = `
<div class="card-h" style="border:0;padding:0;margin-bottom:14px">
<div>
<div style="font-size:18px;font-weight:800;color:var(--t0)">${esc(m.naziv)}</div>
<div style="font-size:12px;color:var(--t2);margin-top:4px">${txt(m.mjesto,'—')} · ${txt(m.razina,'')}</div>
</div>
</div>
<div class="kv">
<div class="k">Organizator</div><div class="v">${txt(m.organizator)}</div>
<div class="k">Razina</div><div class="v">${txt(m.razina)}</div>
<div class="k">Sudionici</div><div class="v">${txt(m.broj_ucesnika)}</div>
<div class="k">Spol/kategorija</div><div class="v">${txt(m.spol_kategorija)}</div>
<div class="k">Godina od</div><div class="v">${txt(m.godina_od)}</div>
<div class="k">Mjesto</div><div class="v">${txt(m.mjesto)}</div>
<div class="k">Izvor</div><div class="v">${m.source_url?'<a href="'+esc(m.source_url)+'" target="_blank">'+esc(m.source_url)+'</a>':'—'}</div>
</div>
${m.napomena ? '<div class="card" style="margin-top:14px"><div class="card-t">Napomena</div><div style="font-size:12px;line-height:1.5;margin-top:6px">'+esc(m.napomena)+'</div></div>' : ''}
`;
openPanel('Manifestacija · '+m.naziv, html);
}
//=========== FORENZIKA ===========
async function loadForenzika(){
const root = $('#pg-forenzika');
let d = _cache.dash;
if(!d){ d = await api('/dashboard'); if(d) _cache.dash = d; }
d = d || {};
const critical = d.critical_alerts || 0;
const warning = d.warning_alerts || 0;
root.innerHTML = `
<div class="kpi-grid">
<div class="kpi r"><div class="kpi-l">Kritičnih</div><div class="kpi-v">${critical}</div><div class="kpi-s">SEVERITY: CRITICAL</div></div>
<div class="kpi"><div class="kpi-l">Upozorenja</div><div class="kpi-v">${warning}</div><div class="kpi-s">SEVERITY: WARNING</div></div>
<div class="kpi b"><div class="kpi-l">Liječničkih isteklih</div><div class="kpi-v">${d.isteki_lijecnicki||0}</div></div>
<div class="kpi g"><div class="kpi-l">Naplaćeno članarina</div><div class="kpi-v" style="font-size:18px">${fmtEur(d.naplaceno_clanarine_god||0)}</div></div>
</div>
<div class="card">
<div class="card-h"><div class="card-t">⚠ Kritični nalazi</div></div>
<div class="alert-card crit">
<div class="at">🔴 Velimir Liverić — politički osjetljiv profil (PEP)</div>
<div class="ad">Manualno označen za pojačanu reviziju. Provjeriti veze s klubovima i potporama.</div>
</div>
<div class="alert-card crit">
<div class="at">🔴 ${critical} aktivnih kritičnih alarma u sustavu</div>
<div class="ad">Većina vezana za istekle liječničke preglede aktivnih sportaša.</div>
</div>
<div class="alert-card">
<div class="at">🟡 Klubovi bez OIB-a</div>
<div class="ad">Provjeriti popunjenost OIB polja u klubovima — utječe na validaciju potpora.</div>
</div>
<div class="alert-card">
<div class="at">🟡 Dospjelih članarina: ${fmtEur(d.dug_clanarine_god||0)}</div>
<div class="ad">Trenutni dug za godinu prema članarinama saveza.</div>
</div>
</div>
<div class="card">
<div class="card-h"><div class="card-t">📋 Liverić — sažetak (PEP)</div></div>
<div class="kv">
<div class="k">Ime</div><div class="v">Velimir Liverić</div>
<div class="k">Funkcija</div><div class="v">Politički eksponiran (PEP)</div>
<div class="k">Status</div><div class="v"><span class="tag rd">PRAĆENJE</span></div>
<div class="k">Razlog</div><div class="v">Politička izloženost — potreba za ručnom revizijom veza prema klubovima i potporama.</div>
<div class="k">Akcija</div><div class="v">Provjeriti veze s primateljima potpora i upravljanjem klubovima.</div>
</div>
</div>
`;
}
//=========== INIT ===========
function init(){
buildNav();
navTo('dashboard');
}
window.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>