Files
damir 8e136351f9 CRISIS FIX: login flow + mobile responsive + token expiry handling
ROOT CAUSE ISOLATED:
Backend POST /api/auth/login, GET/PUT /api/auth/me, POST avatar, POST /logout
all return 200 OK (verified curl). Damirov problem is browser-side:
stale localStorage tokens that don't match current backend → 401 cascade
→ avatar upload appears as 'failed: 401' → profile changes 'lost'.

FIXES:
1. apiAuth() in app.html now:
   - Pre-checks JWT exp claim before request
   - On 401 response: clears localStorage (pgz_access/refresh/user) +
     redirects to /login?reason=unauthorized
   - On JWT expired: redirects to /login?reason=expired

2. login.html displays toast for ?reason=expired/unauthorized

3. Mobile responsive CSS (max-width: 768px):
   - app.html: hamburger menu, sidebar slide-in, full-width drill-down panel
   - sport2.html: KPI grid 2-col, klubovi 1-col, tables horizontal scroll
   - Both: viewport meta + media queries + touch-friendly buttons

4. Mobile menu toggle button + backdrop overlay added

VERIFIED E2E (curl):
- POST /auth/login → 200 + JWT
- GET /auth/me → 200 + telefon persisted
- PUT /auth/me → 200, DB row updated
- POST /auth/me/avatar → 200, file saved + avatar_url returned
- POST /auth/logout → 200, token revoked (next /me returns 401)
2026-05-05 09:14:46 +02:00

1285 lines
92 KiB
HTML
Raw Permalink 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>
<!-- platform.sport.rinet.one — PGŽ Sport Management Platform -->
<!-- v1.0 ERP EDITION | dradulic@outlook.com | 2026-05-04 -->
<!-- Multi-tenant: pgz_admin / savez_admin / klub_admin / clan -->
<html lang="hr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PGŽ Sport Platforma — Upravljanje</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%23003087'/><text x='16' y='22' text-anchor='middle' font-size='14' fill='%23F4C430' font-weight='bold'>SP</text></svg>">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
<style>
:root{
--pgz-blue:#003087; --pgz-blue2:#004CC4; --pgz-gold:#F4C430; --pgz-gold2:#FFD700;
--bg0:#08090e; --bg1:#0d1021; --bg2:#111628; --bg3:#161d35; --bg4:#1c2542;
--rim:#1e2a50; --rim2:#283560;
--t0:#fff; --t1:#e2e6f0; --t2:#8a95b4; --t3:#c0c8de; --t4:#4e5a7a;
--green:#00e88f; --red:#ff2d55; --amber:#f59e0b; --cyan:#00c8e8; --violet:#7c5af0;
--font:'Inter',sans-serif; --mono:'JetBrains Mono',monospace; --r:5px;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;background:var(--bg0);color:var(--t1);font-family:var(--font);font-size:13px;overflow:hidden}
::-webkit-scrollbar{width:4px;height:4px}::-webkit-scrollbar-track{background:var(--bg2)}
::-webkit-scrollbar-thumb{background:var(--rim2);border-radius:2px}
/* ── LOGIN SCREEN ─────────────────────────── */
#login-screen{position:fixed;inset:0;background:var(--bg0);z-index:1000;display:flex;align-items:center;justify-content:center}
.login-box{background:var(--bg1);border:1px solid var(--rim);border-radius:10px;padding:36px 40px;width:380px;max-width:95vw;box-shadow:0 20px 60px rgba(0,0,0,.5)}
.login-logo{text-align:center;margin-bottom:24px}
.login-emblem{width:56px;height:56px;background:var(--pgz-blue);border-radius:10px;margin:0 auto 10px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:18px;font-weight:700;color:var(--pgz-gold)}
.login-title{font-size:18px;font-weight:700;color:var(--t0);text-align:center}
.login-sub{font-size:10px;color:var(--t2);text-align:center;margin-top:4px;font-family:var(--mono)}
.form-g{margin-top:20px}
.form-g label{display:block;font-size:10px;color:var(--t4);font-family:var(--mono);letter-spacing:.5px;margin-bottom:5px;text-transform:uppercase}
.form-g input{width:100%;background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:10px 12px;color:var(--t1);font-family:var(--font);font-size:13px;outline:none;transition:border-color .2s}
.form-g input:focus{border-color:var(--pgz-blue2)}
.btn-login{width:100%;background:var(--pgz-blue);border:none;color:var(--pgz-gold);padding:11px;border-radius:var(--r);font-family:var(--mono);font-size:12px;font-weight:700;letter-spacing:1px;cursor:pointer;margin-top:20px;transition:background .2s}
.btn-login:hover{background:var(--pgz-blue2)}
.login-err{color:var(--red);font-size:11px;text-align:center;margin-top:10px;min-height:16px;font-family:var(--mono)}
.login-roles{margin-top:20px;border-top:1px solid var(--rim);padding-top:16px}
.login-roles p{font-size:9px;color:var(--t4);font-family:var(--mono);letter-spacing:.5px;margin-bottom:8px}
.role-badge{display:inline-flex;align-items:center;gap:5px;background:var(--bg3);border:1px solid var(--rim);border-radius:20px;padding:4px 10px;font-size:9px;color:var(--t2);font-family:var(--mono);margin:2px;cursor:pointer;transition:all .15s}
.role-badge:hover{border-color:var(--pgz-blue2);color:var(--t0)}
/* ── SHELL ─────────────────────────────────── */
#shell{display:flex;height:100vh}
#sb{width:220px;min-width:220px;background:var(--bg1);border-right:1px solid var(--rim);display:flex;flex-direction:column;overflow:hidden}
.sb-head{padding:12px 14px;border-bottom:1px solid var(--rim)}
.sb-brand{font-size:12px;font-weight:700;color:var(--t0);display:flex;align-items:center;gap:8px}
.sb-emblem{width:28px;height:28px;background:var(--pgz-blue);border-radius:5px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:9px;font-weight:700;color:var(--pgz-gold);flex-shrink:0}
.sb-role-chip{background:rgba(244,196,48,.1);border:1px solid rgba(244,196,48,.2);border-radius:3px;font-family:var(--mono);font-size:7px;color:var(--pgz-gold);padding:1px 5px;text-transform:uppercase;letter-spacing:.5px;margin-top:3px;display:inline-block}
#sb nav{flex:1;overflow-y:auto;padding:4px 0}
.nav-grp{padding:9px 12px 2px;font-family:var(--mono);font-size:7px;color:var(--t4);letter-spacing:1.5px;text-transform:uppercase}
.nv{display:flex;align-items:center;gap:7px;padding:7px 12px;color:var(--t2);font-size:11px;font-weight:500;cursor:pointer;border-left:2px solid transparent;transition:all .12s}
.nv:hover{color:var(--t1);background:rgba(255,255,255,.03)}
.nv.on{color:var(--pgz-gold);background:rgba(244,196,48,.05);border-left-color:var(--pgz-gold)}
.nv.warn{color:var(--red)}.nv.warn.on{color:var(--red);background:rgba(255,45,85,.05);border-left-color:var(--red)}
.nv-ico{font-size:13px;width:16px;text-align:center;flex-shrink:0}
.nv-badge{margin-left:auto;font-family:var(--mono);font-size:7px;padding:1px 5px;border-radius:20px}
.nb-r{background:rgba(255,45,85,.2);color:var(--red)}
.nb-a{background:rgba(245,158,11,.15);color:var(--amber)}
.nb-g{background:rgba(0,232,143,.12);color:var(--green)}
.sb-user{padding:10px 14px;border-top:1px solid var(--rim);flex-shrink:0}
.su-name{font-size:11px;font-weight:600;color:var(--t0);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.su-email{font-family:var(--mono);font-size:8px;color:var(--t4);margin-top:1px}
.btn-out{background:none;border:1px solid var(--rim);color:var(--t4);font-size:9px;padding:3px 8px;border-radius:3px;cursor:pointer;font-family:var(--mono);transition:all .15s;margin-top:6px}
.btn-out:hover{border-color:var(--red);color:var(--red)}
#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
#top{height:46px;border-bottom:1px solid var(--rim);display:flex;align-items:center;padding:0 16px;gap:10px;background:rgba(13,16,33,.9);backdrop-filter:blur(8px)}
#page-title{font-size:13px;font-weight:700;color:var(--t0);flex:1}
.tp-pill{background:var(--bg3);border:1px solid var(--rim);padding:3px 9px;border-radius:20px;font-family:var(--mono);font-size:7px;color:var(--t2);display:flex;align-items:center;gap:4px}
.ldot{width:5px;height:5px;border-radius:50%;background:var(--green);animation:pu 1.8s infinite}
@keyframes pu{0%,100%{opacity:1}50%{opacity:.3}}
#content{flex:1;overflow-y:auto;overflow-x:hidden;padding:16px 18px}
/* ── PAGES ────────────────────────────────── */
.pg{display:none}.pg.on{display:block;animation:fi .2s ease}
@keyframes fi{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
/* ── KPIs ─────────────────────────────────── */
.kpi-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:16px}
.kpi{background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:14px;cursor:default;position:relative;overflow:hidden}
.kpi::before{content:'';position:absolute;top:0;left:0;right:0;height:2px}
.kpi.b::before{background:linear-gradient(90deg,var(--pgz-blue2),transparent)}
.kpi.g::before{background:linear-gradient(90deg,var(--pgz-gold),transparent)}
.kpi.r::before{background:linear-gradient(90deg,var(--red),transparent)}
.kpi.c::before{background:linear-gradient(90deg,var(--cyan),transparent)}
.kpi-n{font-family:var(--mono);font-size:24px;font-weight:700;line-height:1;margin-bottom:3px}
.kpi.b .kpi-n{color:var(--pgz-gold)}.kpi.g .kpi-n{color:var(--green)}.kpi.r .kpi-n{color:var(--red)}.kpi.c .kpi-n{color:var(--cyan)}
.kpi-l{font-family:var(--mono);font-size:7px;color:var(--t4);letter-spacing:.8px;text-transform:uppercase}
.kpi-s{font-size:9px;color:var(--t2);margin-top:3px}
/* ── CARDS & TABLES ──────────────────────── */
.cd{background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);overflow:hidden;margin-bottom:12px}
.cd-h{padding:10px 14px;border-bottom:1px solid var(--rim);display:flex;align-items:center;justify-content:space-between;gap:8px}
.cd-t{font-size:11px;font-weight:600;color:var(--t0)}
.cd-b{padding:14px}
.g2{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}
.g3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:12px}
.tw{overflow:auto;max-height:400px}
table.dt{width:100%;border-collapse:collapse;font-size:11px}
table.dt th{padding:6px 10px;text-align:left;font-family:var(--mono);font-size:7px;color:var(--t4);letter-spacing:.8px;text-transform:uppercase;border-bottom:1px solid var(--rim);position:sticky;top:0;background:var(--bg2);cursor:pointer;user-select:none;white-space:nowrap}
table.dt th:hover{color:var(--t1)}
table.dt td{padding:7px 10px;border-bottom:1px solid rgba(30,42,80,.4);vertical-align:middle}
table.dt tr:hover td{background:rgba(0,48,135,.1);cursor:pointer}
.bk{display:inline-block;padding:2px 6px;border-radius:3px;font-family:var(--mono);font-size:7px;font-weight:700}
.bk-g{background:rgba(0,232,143,.1);color:var(--green);border:1px solid rgba(0,232,143,.2)}
.bk-r{background:rgba(255,45,85,.1);color:var(--red);border:1px solid rgba(255,45,85,.2)}
.bk-a{background:rgba(245,158,11,.1);color:var(--amber);border:1px solid rgba(245,158,11,.2)}
.bk-b{background:rgba(0,48,135,.2);color:#7eb0ff;border:1px solid rgba(0,76,196,.3)}
.bk-c{background:rgba(0,200,232,.08);color:var(--cyan);border:1px solid rgba(0,200,232,.15)}
.bk-gld{background:rgba(244,196,48,.1);color:var(--pgz-gold);border:1px solid rgba(244,196,48,.2)}
.mn{font-family:var(--mono)}
.ev{font-family:var(--mono);text-align:right;color:var(--green)}
/* ── BUTTONS ──────────────────────────────── */
.btn{background:var(--bg3);border:1px solid var(--rim2);color:var(--t2);padding:5px 12px;border-radius:var(--r);font-size:11px;cursor:pointer;transition:all .15s;font-family:var(--font)}
.btn:hover{border-color:var(--pgz-blue2);color:var(--t0)}
.btn-sm{padding:3px 8px;font-size:10px}
.btn-pgz{background:rgba(0,48,135,.2);border-color:rgba(0,76,196,.4);color:#7eb0ff}
.btn-pgz:hover{background:rgba(0,48,135,.35)}
.btn-grn{background:rgba(0,232,143,.1);border-color:rgba(0,232,143,.2);color:var(--green)}
.btn-red{background:rgba(255,45,85,.1);border-color:rgba(255,45,85,.2);color:var(--red)}
/* ── FBAR ─────────────────────────────────── */
.fbar{display:flex;gap:7px;margin-bottom:10px;flex-wrap:wrap}
.fbar input,.fbar select{background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:7px 10px;color:var(--t1);font-size:12px;outline:none;transition:border-color .2s}
.fbar input{flex:1;min-width:150px}
.fbar input::placeholder{color:var(--t4)}
.fbar input:focus,.fbar select:focus{border-color:var(--pgz-blue2)}
/* ── TABS ─────────────────────────────────── */
.tabs{display:flex;border-bottom:1px solid var(--rim);margin-bottom:12px}
.tab{padding:7px 13px;font-size:11px;font-weight:500;color:var(--t4);cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;white-space:nowrap}
.tab:hover{color:var(--t1)}
.tab.on{color:var(--pgz-gold);border-bottom-color:var(--pgz-gold)}
.tab-c{display:none}.tab-c.on{display:block}
/* ── VIEW TOGGLE ──────────────────────────── */
.vt{display:flex;gap:2px;background:var(--bg3);border:1px solid var(--rim);border-radius:4px;padding:2px}
.vt-btn{padding:3px 8px;border-radius:3px;cursor:pointer;font-size:10px;color:var(--t2);transition:all .15s}
.vt-btn.on{background:var(--rim2);color:var(--t0)}
/* ── PANEL ────────────────────────────────── */
#panel{position:fixed;top:0;right:-600px;width:580px;height:100vh;background:var(--bg1);border-left:1px solid var(--rim2);z-index:200;transition:right .3s;display:flex;flex-direction:column}
#panel.open{right:0}
#panel-hdr{padding:12px 14px;border-bottom:1px solid var(--rim);display:flex;align-items:center;justify-content:space-between}
#panel-hdr-t{font-size:13px;font-weight:700;color:var(--t0)}
#panel-body{flex:1;overflow-y:auto;padding:14px}
#panel-close{background:var(--bg3);border:1px solid var(--rim);width:26px;height:26px;border-radius:4px;cursor:pointer;font-size:14px;color:var(--t2);display:flex;align-items:center;justify-content:center}
#panel-close:hover{color:var(--t0)}
#panel-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:199;backdrop-filter:blur(2px)}
/* ── LOADING ──────────────────────────────── */
.ld{display:flex;align-items:center;justify-content:center;gap:7px;padding:30px;color:var(--t4);font-family:var(--mono);font-size:9px;letter-spacing:.5px}
.sp{width:14px;height:14px;border:2px solid var(--rim2);border-top-color:var(--pgz-blue2);border-radius:50%;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.empty{padding:28px;text-align:center;color:var(--t4);font-family:var(--mono);font-size:9px}
/* ── SROW ─────────────────────────────────── */
.srow{display:flex;justify-content:space-between;align-items:center;padding:5px 0;border-bottom:1px solid rgba(30,42,80,.4)}
.srow:last-child{border:none}
.srow label{font-size:10px;color:var(--t2)}
.srow span{font-family:var(--mono);font-size:11px;color:var(--pgz-gold)}
/* ── SPORTAS HNS STYLE ───────────────────── */
.sportas-hero{background:linear-gradient(135deg,var(--bg3),var(--bg2));border:1px solid var(--rim);border-radius:var(--r);padding:16px;margin-bottom:12px;display:flex;gap:16px}
.sh-ava{width:80px;height:100px;background:var(--bg0);border:2px solid var(--rim2);border-radius:6px;flex-shrink:0;overflow:hidden;display:flex;align-items:center;justify-content:center;font-size:36px}
.sh-info{flex:1;min-width:0}
.sh-name{font-size:18px;font-weight:700;color:var(--t0);margin-bottom:4px}
.sh-sub{font-size:10px;color:var(--t2);font-family:var(--mono);margin-bottom:8px}
.sh-badges{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:10px}
.sh-stats{display:grid;grid-template-columns:repeat(6,1fr);gap:6px}
.sh-stat{background:var(--bg0);border-radius:4px;padding:6px;text-align:center}
.sh-stat-n{font-family:var(--mono);font-size:16px;font-weight:700;color:var(--pgz-gold);line-height:1}
.sh-stat-l{font-size:7px;color:var(--t4);letter-spacing:.3px;margin-top:2px}
.sezona-row{display:grid;grid-template-columns:80px 1fr 50px 50px 50px 50px 50px 60px;gap:0;padding:8px 0;border-bottom:1px solid rgba(30,42,80,.3);align-items:center;font-size:11px}
.sezona-row:hover{background:rgba(0,48,135,.08)}
.sezona-hdr{font-family:var(--mono);font-size:7px;color:var(--t4);letter-spacing:.5px;text-transform:uppercase;border-bottom:1px solid var(--rim);padding-bottom:6px;margin-bottom:2px}
/* ── MEDICAL ALERT ───────────────────────── */
.med-alert{background:rgba(255,45,85,.06);border:1px solid rgba(255,45,85,.2);border-radius:var(--r);padding:10px 14px;margin-bottom:8px;display:flex;align-items:center;gap:10px}
.med-ok{background:rgba(0,232,143,.05);border-color:rgba(0,232,143,.15)}
.med-warn{background:rgba(245,158,11,.06);border-color:rgba(245,158,11,.2)}
/* ── NETWORK ──────────────────────────────── */
#net-wrap{background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);position:relative;overflow:hidden;height:520px}
#net-svg{width:100%;height:100%}
.net-toolbar{display:flex;gap:8px;margin-bottom:10px;flex-wrap:wrap}
#net-info{position:absolute;bottom:12px;right:12px;background:rgba(8,9,14,.96);border:1px solid var(--rim2);border-radius:var(--r);padding:10px 14px;max-width:260px;display:none;backdrop-filter:blur(8px);z-index:10}
.net-legend{position:absolute;top:10px;left:10px;background:rgba(8,9,14,.85);border:1px solid var(--rim);border-radius:var(--r);padding:8px 12px;backdrop-filter:blur(8px)}
.nl-item{display:flex;align-items:center;gap:6px;font-size:9px;color:var(--t2);margin-bottom:4px}
.nl-item:last-child{margin:0}
.nl-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}
/* ── MEMBERSHIP CRM ──────────────────────── */
.clan-card{background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:12px;display:flex;gap:10px;cursor:pointer;transition:all .2s;margin-bottom:6px}
.clan-card:hover{border-color:var(--pgz-blue2);background:rgba(0,48,135,.05)}
.clan-ava{width:40px;height:40px;border-radius:5px;background:var(--bg3);border:1px solid var(--rim2);overflow:hidden;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.clan-info{flex:1;min-width:0}
.ci-name{font-weight:600;color:var(--t0);font-size:12px}
.ci-meta{font-family:var(--mono);font-size:8px;color:var(--t2);margin-top:2px}
.ci-status{display:flex;gap:4px;margin-top:5px;flex-wrap:wrap}
/* ── FORM ─────────────────────────────────── */
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px}
.form-field{display:flex;flex-direction:column;gap:4px}
.form-field label{font-size:9px;color:var(--t4);font-family:var(--mono);letter-spacing:.5px;text-transform:uppercase}
.form-field input,.form-field select,.form-field textarea{background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:8px 10px;color:var(--t1);font-size:12px;outline:none;transition:border-color .2s;font-family:var(--font)}
.form-field input:focus,.form-field select:focus,.form-field textarea:focus{border-color:var(--pgz-blue2)}
.form-field textarea{resize:vertical;min-height:60px}
/* ── ALERT BANNER ────────────────────────── */
.alert-bar{background:rgba(255,45,85,.08);border:1px solid rgba(255,45,85,.2);border-radius:var(--r);padding:10px 14px;display:flex;align-items:center;gap:10px;margin-bottom:12px}
.alert-bar.warn{background:rgba(245,158,11,.08);border-color:rgba(245,158,11,.2)}
.alert-ico{font-size:16px;flex-shrink:0}
.alert-txt{flex:1;font-size:11px;color:var(--t3)}
.alert-txt strong{color:var(--red)}
.alert-bar.warn .alert-txt strong{color:var(--amber)}
/* ── MOBILE ──────────────────────────────── */
@media(max-width:900px){
#sb{display:none}
.kpi-grid{grid-template-columns:1fr 1fr}
.g2,.g3{grid-template-columns:1fr}
#panel{width:100vw}
.sh-stats{grid-template-columns:repeat(3,1fr)}
}
</style>
<script src="/static/oib_format.js" defer></script>
</head>
<body>
<!-- ══ LOGIN SCREEN ══ -->
<div id="login-screen">
<div class="login-box">
<div class="login-logo">
<div class="login-emblem">PGŽ</div>
<div class="login-title">Sport Platforma</div>
<div class="login-sub">PRIMORSKO-GORANSKA ŽUPANIJA</div>
</div>
<div style="margin-bottom:16px">
<div class="form-g"><label>Email adresa</label><input type="email" id="login-email" placeholder="vaš@email.hr" autocomplete="email"></div>
<div class="form-g" style="margin-top:10px"><label>Lozinka</label><input type="password" id="login-pwd" placeholder="••••••••" autocomplete="current-password"></div>
</div>
<button class="btn-login" onclick="doLogin()">PRIJAVI SE →</button>
<div class="login-err" id="login-err"></div>
<div class="login-roles">
<p>DEMO ULOGE — klikni za brzi login:</p>
<span class="role-badge" onclick="quickLogin('damir@pgz.hr','PGZ2026!')">👑 PGŽ Admin</span>
<span class="role-badge" onclick="quickLogin('tajnik.zamet@rk.hr','admin123')">🏟 Klub Admin</span>
</div>
</div>
</div>
<!-- ══ MAIN APP ══ -->
<div id="shell" style="display:none">
<nav id="sb">
<div class="sb-head">
<div class="sb-brand">
<div class="sb-emblem">PGŽ</div>
<div>
<div style="font-size:11px;font-weight:700">Sport Platforma</div>
<div class="sb-role-chip" id="sb-role-chip"></div>
</div>
</div>
</div>
<nav id="sb-nav">
<div class="nav-grp">Pregled</div>
<div class="nv on" onclick="G('dash')"><span class="nv-ico"></span> Dashboard</div>
<div id="nav-admin" style="display:none">
<div class="nav-grp">Registar PGŽ</div>
<div class="nv" onclick="G('savezi')"><span class="nv-ico"></span> Savezi<span class="nv-badge nb-g" id="nb-savezi"></span></div>
<div class="nv" onclick="G('klubovi')"><span class="nv-ico"></span> Klubovi</div>
<div class="nv" onclick="G('sportasi')"><span class="nv-ico"></span> Sportaši</div>
<div class="nav-grp">Analitika</div>
<div class="nv" onclick="G('financije')"><span class="nv-ico"></span> Financije</div>
<div class="nv" onclick="G('mreza')"><span class="nv-ico"></span> Graf mreža</div>
<div class="nv warn" onclick="G('forenzika')"><span class="nv-ico"></span> Forenzika</div>
<div class="nav-grp">Sustav</div>
<div class="nv" onclick="G('korisnici')"><span class="nv-ico"></span> Korisnici</div>
</div>
<div id="nav-klub">
<div class="nav-grp">Moj Klub</div>
<div class="nv" onclick="G('k-clanovi')"><span class="nv-ico">👥</span> Evidencija članova<span class="nv-badge nb-g" id="nb-clan"></span></div>
<div class="nv" onclick="G('k-clanarine')"><span class="nv-ico"></span> Članarine<span class="nv-badge nb-a" id="nb-clan-dug"></span></div>
<div class="nv" onclick="G('k-lijecnicki')"><span class="nv-ico">🏥</span> Liječnički<span class="nv-badge nb-r" id="nb-lij-alert"></span></div>
<div class="nv" onclick="G('k-treninzi')"><span class="nv-ico">📋</span> Treninzi & Natjecanja</div>
<div class="nv" onclick="G('k-dokumenti')"><span class="nv-ico">📁</span> Dokumenti (DMS)</div>
<div class="nv" onclick="G('k-financije')"><span class="nv-ico">💰</span> Računovodstvo</div>
</div>
<div id="nav-clan" style="display:none">
<div class="nav-grp">Moj Profil</div>
<div class="nv" onclick="G('c-profil')"><span class="nv-ico"></span> Moji podaci</div>
<div class="nv" onclick="G('c-potvrde')"><span class="nv-ico">📄</span> Moje potvrde</div>
</div>
</nav>
<div class="sb-user">
<div class="su-name" id="sb-name"></div>
<div class="su-email" id="sb-email"></div>
<button class="btn-out" onclick="doLogout()">Odjava</button>
</div>
</nav>
<div id="main">
<div id="top">
<div id="page-title">Dashboard</div>
<div class="tp-pill"><div class="ldot"></div>LIVE</div>
<div class="tp-pill" id="tp-datum"></div>
<button class="btn btn-sm btn-pgz" id="btn-goto-portal" onclick="window.open('/','_blank')" title="Javni portal">↗ Portal</button>
</div>
<div id="content">
<!-- ══ DASHBOARD ══ -->
<div id="pg-dash" class="pg on">
<div id="dash-kpi" class="kpi-grid"></div>
<div id="dash-alerts"></div>
<div class="g2">
<div id="dash-top" class="cd"><div class="cd-h"><div class="cd-t" id="dash-top-title">Top aktivnosti</div></div><div class="cd-b" id="dash-top-body"><div class="ld"><div class="sp"></div></div></div></div>
<div class="cd"><div class="cd-h"><div class="cd-t">Brzi pristup</div></div><div class="cd-b" id="dash-quick-links"></div></div>
</div>
</div>
<!-- ══ SAVEZI (admin) ══ -->
<div id="pg-savezi" class="pg">
<div class="fbar">
<input id="sv-q" type="text" placeholder="Pretraži saveze…" oninput="loadSaveziPlatform()">
<select id="sv-sport" onchange="loadSaveziPlatform()" style="cursor:pointer"><option value="">Svi sportovi</option></select>
</div>
<div id="sv-grid" class="g3"></div>
</div>
<!-- ══ KLUBOVI (admin) ══ -->
<div id="pg-klubovi" class="pg">
<div class="fbar">
<input id="kl-q" type="text" placeholder="Naziv kluba…" oninput="loadKluboviPlatform()">
<select id="kl-sport2" onchange="loadKluboviPlatform()" style="cursor:pointer"><option value="">Svi sportovi</option></select>
<select id="kl-grad2" onchange="loadKluboviPlatform()" style="cursor:pointer"><option value="">Svi gradovi</option></select>
<label style="display:flex;align-items:center;gap:5px;font-size:11px;color:var(--t2)"><input type="checkbox" id="kl-nk2" onchange="loadKluboviPlatform()"> Nositelji</label>
<div class="vt"><div class="vt-btn on" onclick="setKlView('card')" id="kl-vt-c"></div><div class="vt-btn" onclick="setKlView('table')" id="kl-vt-t"></div></div>
</div>
<div id="kl-cards2" class="g3"></div>
<div id="kl-table2" style="display:none"><div class="cd"><div class="tw"><table class="dt"><thead><tr><th onclick="sortDt('kl-dt',0)">Naziv</th><th>Sport</th><th>Grad</th><th>Predsjednik</th><th>NK</th><th style="text-align:right">Čl.</th><th></th></tr></thead><tbody id="kl-dt"></tbody></table></div></div></div>
</div>
<!-- ══ SPORTAŠI (admin) ══ -->
<div id="pg-sportasi" class="pg">
<div class="fbar">
<input id="sp-q2" type="text" placeholder="Ime ili prezime…" oninput="loadSportasiPlatform()">
<select id="sp-hoo2" onchange="loadSportasiPlatform()" style="cursor:pointer">
<option value="">Sve HOO kategorije</option>
<option value="1">HOO I</option><option value="2">HOO II</option>
<option value="3">HOO III</option><option value="4">HOO IV</option>
</select>
<label style="display:flex;align-items:center;gap:5px;font-size:11px;color:var(--t2)"><input type="checkbox" id="sp-repr2" onchange="loadSportasiPlatform()"> Repr.</label>
</div>
<div id="sp-grid" class="g3"></div>
</div>
<!-- ══ KLUB - ČLANOVI CRM ══ -->
<div id="pg-k-clanovi" class="pg">
<div class="fbar">
<input id="kcl-q" type="text" placeholder="Pretraži članove…" oninput="loadKlubClanovi()">
<select id="kcl-kat" onchange="loadKlubClanovi()" style="cursor:pointer"><option value="">Sve kategorije</option></select>
<select id="kcl-aktivan" onchange="loadKlubClanovi()" style="cursor:pointer"><option value="">Svi statusi</option><option value="1">Aktivni</option><option value="0">Neaktivni</option></select>
<button class="btn btn-grn btn-sm" onclick="openNovClan()">+ Novi član</button>
<div class="vt"><div class="vt-btn on" onclick="setClView('list')" id="kcl-vt-l"></div><div class="vt-btn" onclick="setClView('card')" id="kcl-vt-c"></div></div>
</div>
<div id="kcl-list"></div>
<div id="kcl-cards" style="display:none" class="g3"></div>
</div>
<!-- ══ KLUB - ČLANARINE ══ -->
<div id="pg-k-clanarine" class="pg">
<div class="fbar">
<select id="clar-god" onchange="loadClanarine()" style="cursor:pointer"><option value="">Sve godine</option></select>
<select id="clar-status" onchange="loadClanarine()" style="cursor:pointer"><option value="">Svi statusi</option><option value="placeno">Plaćeno</option><option value="djelomicno">Djelomično</option><option value="neplaceno">Neplaćeno</option></select>
<input id="clar-q" type="text" placeholder="Ime člana…" oninput="loadClanarine()">
<button class="btn btn-grn btn-sm" onclick="exportCSV('clar-dt')">⬇ CSV</button>
</div>
<div id="clar-summary" class="kpi-grid" style="grid-template-columns:repeat(3,1fr)"></div>
<div class="cd"><div class="cd-h"><div class="cd-t">Pregled članarina</div></div>
<div class="tw"><table class="dt" id="clar-dt">
<thead><tr><th onclick="sortDt('clar-dt',0)">Član</th><th>Kategorija</th><th onclick="sortDt('clar-dt',2)">Godina</th><th style="text-align:right">Propisano</th><th style="text-align:right">Plaćeno</th><th style="text-align:right">Dug</th><th>Status</th><th>Akcija</th></tr></thead>
<tbody id="clar-tbody"></tbody>
</table></div>
</div>
</div>
<!-- ══ KLUB - LIJEČNIČKI ══ -->
<div id="pg-k-lijecnicki" class="pg">
<div id="lij-alerts"></div>
<div class="fbar">
<input id="lij-q" type="text" placeholder="Ime člana…" oninput="loadLijecnicki()">
<select id="lij-status" onchange="loadLijecnicki()" style="cursor:pointer">
<option value="">Svi statusi</option>
<option value="istekao">⚠ Istekao</option>
<option value="uskoro_istece">🔔 Uskoro ističe</option>
<option value="validan">✓ Validan</option>
</select>
<button class="btn btn-grn btn-sm" onclick="openNoviPregled()">+ Novi pregled</button>
</div>
<div class="cd"><div class="cd-h"><div class="cd-t">Liječnički pregledi</div></div>
<div class="tw"><table class="dt" id="lij-dt">
<thead><tr><th onclick="sortDt('lij-dt',0)">Član</th><th onclick="sortDt('lij-dt',1)">Datum pregleda</th><th onclick="sortDt('lij-dt',2)">Vrijedi do</th><th>Status</th><th>Ustanova</th><th>ZZJZ</th><th>Nalaz</th><th>Akcija</th></tr></thead>
<tbody id="lij-tbody"></tbody>
</table></div>
</div>
</div>
<!-- ══ KLUB - TRENINZI ══ -->
<div id="pg-k-treninzi" class="pg">
<div class="alert-bar warn"><div class="alert-ico">🔧</div><div class="alert-txt">Modul u razvoju — evidencija treninga, raspored natjecanja, planeriranje termina. <strong>Q3 2026</strong></div></div>
<div class="cd"><div class="cd-h"><div class="cd-t">Nadolazeći termini</div></div><div class="cd-b"><div class="empty">Nema unesenih termina</div></div></div>
</div>
<!-- ══ KLUB - DOKUMENTI DMS ══ -->
<div id="pg-k-dokumenti" class="pg">
<div class="fbar">
<input type="text" placeholder="Pretraži dokumente…">
<select style="cursor:pointer"><option>Svi tipovi</option><option>Statuti</option><option>Pravilnici</option><option>Ugovori</option><option>Zapisnici</option></select>
<button class="btn btn-pgz btn-sm" onclick="$_id('upload-modal').style.display='flex'">+ Upload</button>
</div>
<div id="dms-docs" class="g3">
<div class="cd"><div class="cd-h"><div class="cd-t">📄 Statut kluba</div><span class="bk bk-b">PDF</span></div><div class="cd-b" style="padding:10px"><div class="srow"><label>Datum</label><span>2024-01-15</span></div><div class="srow"><label>Verzija</label><span>v3.2</span></div><div style="display:flex;gap:4px;margin-top:8px"><button class="btn btn-sm">👁 View</button><button class="btn btn-sm">⬇ Download</button><button class="btn btn-sm">🔗 Share</button></div></div></div>
<div class="cd" style="border:2px dashed var(--rim);opacity:.5;cursor:pointer"><div class="cd-b" style="text-align:center;padding:20px"><div style="font-size:28px;margin-bottom:8px">+</div><div style="font-size:11px;color:var(--t4)">Dodaj dokument</div></div></div>
</div>
</div>
<!-- ══ KLUB - RAČUNOVODSTVO ══ -->
<div id="pg-k-financije" class="pg">
<div class="alert-bar warn"><div class="alert-ico">🔧</div><div class="alert-txt">Računovodstveni modul — knjige, putni nalozi, e-Račun integracija. <strong>Q4 2026</strong></div></div>
<div class="g2">
<div class="cd"><div class="cd-h"><div class="cd-t">Brzi pregled blagajne</div></div><div class="cd-b"><div class="srow"><label>Prihodi YTD</label><span style="color:var(--green)">€0.00</span></div><div class="srow"><label>Rashodi YTD</label><span style="color:var(--red)">€0.00</span></div><div class="srow"><label>Saldo</label><span>€0.00</span></div></div></div>
<div class="cd"><div class="cd-h"><div class="cd-t">Putni nalozi</div></div><div class="cd-b"><div class="empty">Nema unesenih naloga</div></div></div>
</div>
</div>
<!-- ══ FINANCIJE (admin) ══ -->
<div id="pg-financije" class="pg">
<div class="fbar">
<select id="fin-god" onchange="loadFinancije()" style="cursor:pointer"><option value="">Sve godine</option></select>
<select id="fin-sport" onchange="loadFinancije()" style="cursor:pointer"><option value="">Svi sportovi</option></select>
<input id="fin-q" type="text" placeholder="Naziv kluba/korisnika…" oninput="loadFinancije()">
</div>
<div id="fin-kpi" class="kpi-grid"></div>
<div class="g2">
<div class="cd" style="height:280px"><div class="cd-h"><div class="cd-t">Raspodjela po sportovima</div><div class="vt"><div class="vt-btn on" onclick="switchFin('bar')">Bar</div><div class="vt-btn" onclick="switchFin('pie')">Pie</div></div></div><div class="cd-b" style="height:210px"><canvas id="fin-chart"></canvas></div></div>
<div class="cd"><div class="cd-h"><div class="cd-t">Top primatelji</div></div><div class="tw" style="max-height:220px"><table class="dt"><thead><tr><th>Korisnik</th><th>Sport</th><th style="text-align:right">Iznos</th></tr></thead><tbody id="fin-top-tb"></tbody></table></div></div>
</div>
<div class="cd"><div class="cd-h"><div class="cd-t">Svi korisnici</div><div id="fin-total" style="font-family:var(--mono);font-size:10px;color:var(--pgz-gold)"></div></div><div class="tw"><table class="dt" id="fin-dt"><thead><tr><th onclick="sortDt('fin-dt',0)">Korisnik</th><th onclick="sortDt('fin-dt',1)">Sport</th><th onclick="sortDt('fin-dt',2)">Razina</th><th onclick="sortDt('fin-dt',3)">Godina</th><th onclick="sortDt('fin-dt',4)">Vrsta</th><th onclick="sortDt('fin-dt',5)" style="text-align:right">Iznos EUR</th><th>Dokument</th></tr></thead><tbody id="fin-tbody"></tbody></table></div></div>
</div>
<!-- ══ MREŽA ISTRAŽIVAČ ══ -->
<div id="pg-mreza" class="pg">
<div class="net-toolbar">
<input id="net-osoba" type="text" placeholder="🔍 Osoba…" style="flex:1;background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:7px 11px;color:var(--t1);font-size:12px;outline:none">
<input id="net-klub" type="text" placeholder="🏟 Klub / Savez…" style="flex:1;background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:7px 11px;color:var(--t1);font-size:12px;outline:none">
<input id="net-entitet" type="text" placeholder="🏢 Tvrtka…" style="flex:1;background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:7px 11px;color:var(--t1);font-size:12px;outline:none">
<button class="btn btn-pgz" onclick="searchNetwork()">🔍 Istraži</button>
<button class="btn" onclick="resetNetwork()">↺ Reset</button>
<select id="net-depth" style="background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:7px;color:var(--t2);cursor:pointer">
<option value="1">Dubina 1</option><option value="2" selected>Dubina 2</option><option value="3">Dubina 3</option>
</select>
</div>
<div id="net-wrap">
<div class="net-legend">
<div style="font-family:var(--mono);font-size:7px;color:var(--t4);letter-spacing:1px;margin-bottom:6px">ČVOROVI</div>
<div class="nl-item"><div class="nl-dot" style="background:var(--cyan)"></div>Osoba</div>
<div class="nl-item"><div class="nl-dot" style="background:var(--green)"></div>Udruga/Klub</div>
<div class="nl-item"><div class="nl-dot" style="background:var(--violet)"></div>Tvrtka</div>
<div class="nl-item"><div class="nl-dot" style="background:var(--pgz-gold)"></div>Javno tijelo</div>
<div class="nl-item"><div class="nl-dot" style="background:var(--red)"></div>⚠ Forenzički</div>
</div>
<svg id="net-svg"></svg>
<div id="net-info">
<div style="font-family:var(--mono);font-size:7px;color:var(--t4);margin-bottom:4px" id="ni-type"></div>
<div style="font-size:13px;font-weight:700;color:var(--t0);margin-bottom:6px" id="ni-name"></div>
<div style="font-size:10px;color:var(--t3);line-height:1.6" id="ni-detail"></div>
<button class="btn btn-sm" style="margin-top:8px" onclick="expandNode()">Proširi veze</button>
</div>
<div class="ld" id="net-ld" style="position:absolute;inset:0;background:rgba(8,9,14,.9)"><div class="sp"></div><span style="font-family:var(--mono);font-size:9px">Gradim mrežu…</span></div>
</div>
<div style="margin-top:8px;font-family:var(--mono);font-size:8px;color:var(--t4)" id="net-stats">Drag = pomicanje čvora · Scroll = zoom · Klik = info · Shift+klik = proširi</div>
</div>
<!-- ══ FORENZIKA ══ -->
<div id="pg-forenzika" class="pg">
<div class="alert-bar"><div class="alert-ico"></div><div class="alert-txt"><strong>UPOZORENJE:</strong> Forenzički podaci klasificirani. Pristup samo za ovlaštene osobe PGŽ.</div></div>
<div id="foren-wrap"><div class="ld"><div class="sp"></div></div></div>
</div>
<!-- ══ KORISNICI (admin) ══ -->
<div id="pg-korisnici" class="pg">
<div class="fbar">
<input type="text" placeholder="Email ili ime…">
<select style="cursor:pointer"><option>Sve uloge</option><option>pgz_admin</option><option>savez_admin</option><option>klub_admin</option><option>clan</option></select>
<button class="btn btn-grn btn-sm" onclick="openNovKorisnik()">+ Novi korisnik</button>
</div>
<div id="usr-list"><div class="ld"><div class="sp"></div></div></div>
</div>
<!-- ══ MOJI PODACI (clan) ══ -->
<div id="pg-c-profil" class="pg">
<div class="cd"><div class="cd-h"><div class="cd-t">Moji osobni podaci</div></div><div class="cd-b" id="c-profil-body"><div class="ld"><div class="sp"></div></div></div></div>
</div>
</div><!-- /content -->
</div><!-- /main -->
</div><!-- /shell -->
<!-- ══ DETAIL PANEL ══ -->
<div id="panel">
<div id="panel-hdr">
<div id="panel-hdr-t">Detalji</div>
<div id="panel-close" onclick="closePanel()">×</div>
</div>
<div id="panel-body"></div>
</div>
<div id="panel-overlay" onclick="closePanel()"></div>
<script>
// ── CONFIG ──────────────────────────────────────────────────────
const API = '/sport/api';
// ── STATE ──────────────────────────────────────────────────────
let _auth = null, _role = null, _klubId = null, _savezId = null;
let _netData = {nodes:[], edges:[]}, _selectedNode = null;
const $ = id => document.getElementById(id);
const fmtE = n => n==null?'':'€'+Number(n).toLocaleString('hr-HR',{maximumFractionDigits:0});
const fmtN = n => n==null?'':Number(n).toLocaleString('hr-HR');
const dt = () => new Date().toLocaleString('hr-HR');
// ── AUTH ────────────────────────────────────────────────────────
async function doLogin(){
const email = $('login-email').value.trim();
const pwd = $('login-pwd').value;
$('login-err').textContent = '';
if(!email||!pwd){$('login-err').textContent='Unesite email i lozinku';return}
try{
const r = await fetch(`${API}/auth/login`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email,password:pwd})});
if(!r.ok){const e=await r.json();$('login-err').textContent=e.detail||'Greška pri prijavi';return}
const d = await r.json();
onLoginSuccess(d);
}catch(e){$('login-err').textContent='Mrežna greška: '+e.message}
}
async function quickLogin(email, pwd){
$('login-email').value=email; $('login-pwd').value=pwd; doLogin();
}
function onLoginSuccess(d){
_auth=d; _role=d.role; _klubId=d.klub_id; _savezId=d.savez_id;
sessionStorage.setItem('pgz_tok',d.token);
$('login-screen').style.display='none';
$('shell').style.display='flex';
$('sb-name').textContent=d.name||d.email;
$('sb-email').textContent=d.email;
$('sb-role-chip').textContent=_role;
$('tp-datum').textContent=dt();
buildNav();
initDashboard();
}
function doLogout(){
sessionStorage.removeItem('pgz_tok');
_auth=null; _role=null;
$('shell').style.display='none';
$('login-screen').style.display='flex';
$('login-err').textContent='';
$('login-pwd').value='';
}
// Check existing session
const savedTok = sessionStorage.getItem('pgz_tok');
if(savedTok){
fetch(`${API}/auth/me`,{headers:{Authorization:'Bearer '+savedTok}}).then(r=>r.json()).then(d=>{
if(d.email){fetch(`${API}/auth/login`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({_tok:savedTok})}).catch(()=>{});
// Use cached role from token
try{const pl=JSON.parse(atob(savedTok.split('.')[1]));
onLoginSuccess({...d,token:savedTok,role:pl.role||d.role,klub_id:pl.klub_id,savez_id:pl.savez_id});
}catch{}}
}).catch(()=>{});
}
// ── NAV ─────────────────────────────────────────────────────────
function buildNav(){
$('nav-admin').style.display = ['pgz_admin','savez_admin'].includes(_role)?'block':'none';
$('nav-clan').style.display = _role==='clan'?'block':'none';
if(_role==='pgz_admin'){
$('dash-quick-links').innerHTML=buildQuickLinks([
{icon:'◈',label:'Savezi',page:'savezi'},{icon:'⬡',label:'Klubovi',page:'klubovi'},
{icon:'€',label:'Financije',page:'financije'},{icon:'○',label:'Mreža',page:'mreza'},
{icon:'⚠',label:'Forenzika',page:'forenzika'},{icon:'⊕',label:'Korisnici',page:'korisnici'}
]);
} else if(_role==='klub_admin'){
$('dash-quick-links').innerHTML=buildQuickLinks([
{icon:'👥',label:'Evidencija članova',page:'k-clanovi'},{icon:'€',label:'Članarine',page:'k-clanarine'},
{icon:'🏥',label:'Liječnički',page:'k-lijecnicki'},{icon:'📁',label:'Dokumenti',page:'k-dokumenti'}
]);
}
}
function buildQuickLinks(items){
return items.map(i=>`<button class="btn btn-pgz" style="display:block;width:100%;text-align:left;margin-bottom:6px;padding:9px 12px" onclick="G('${i.page}')"><span style="font-size:14px">${i.icon}</span> ${i.label}</button>`).join('');
}
// ── ROUTING ─────────────────────────────────────────────────────
function G(id){
document.querySelectorAll('.pg').forEach(p=>p.classList.remove('on'));
document.querySelectorAll('.nv').forEach(n=>n.classList.remove('on'));
$('pg-'+id)?.classList.add('on');
document.querySelectorAll('.nv').forEach(n=>{if(n.getAttribute('onclick')?.includes("'"+id+"'"))n.classList.add('on')});
const titles={dash:'Dashboard',savezi:'Savezi PGŽ',klubovi:'Registar Klubova',sportasi:'Sportaši',financije:'Financije & Potpore',mreza:'Graf mreža — Istraživač',forenzika:'Forenzička analiza','k-clanovi':'Evidencija članova','k-clanarine':'Članarine','k-lijecnicki':'Liječnički pregledi','k-treninzi':'Treninzi & Natjecanja','k-dokumenti':'Dokumenti (DMS)','k-financije':'Računovodstvo',korisnici:'Upravljanje korisnicima','c-profil':'Moji podaci'};
$('page-title').textContent=titles[id]||id;
// Lazy load
if(id==='savezi') loadSaveziPlatform();
else if(id==='klubovi') loadKluboviPlatform();
else if(id==='sportasi') loadSportasiPlatform();
else if(id==='financije') loadFinancije();
else if(id==='mreza' && _netData.nodes.length===0) resetNetwork();
else if(id==='k-clanovi') loadKlubClanovi();
else if(id==='k-clanarine') loadClanarine();
else if(id==='k-lijecnicki') loadLijecnicki();
else if(id==='forenzika') loadForenzika();
else if(id==='korisnici') loadKorisnici();
}
// ── DASHBOARD ───────────────────────────────────────────────────
async function initDashboard(){
try{
const d=await fetch(`${API}/dashboard`).then(r=>r.json());
const isKlub = _role==='klub_admin';
if(isKlub && _klubId){
$('dash-kpi').innerHTML=`
<div class="kpi b"><div class="kpi-n" id="dk-cl">…</div><div class="kpi-l">Članova</div></div>
<div class="kpi r"><div class="kpi-n" id="dk-lij">…</div><div class="kpi-l">⚠ Liječnički</div></div>
<div class="kpi g"><div class="kpi-n" id="dk-dug">…</div><div class="kpi-l">Dug clanarine</div></div>
<div class="kpi c"><div class="kpi-n"></div><div class="kpi-l">Potpore 2025</div></div>`;
loadKlubDashKpi();
} else {
$('dash-kpi').innerHTML=`
<div class="kpi b"><div class="kpi-n">${fmtN(d.aktivnih_saveza)}</div><div class="kpi-l">Saveza</div></div>
<div class="kpi g"><div class="kpi-n">${fmtN(d.aktivnih_klubova)}</div><div class="kpi-l">Klubova</div></div>
<div class="kpi c"><div class="kpi-n">${fmtN(d.aktivnih_clanova)}</div><div class="kpi-l">Sportaša</div></div>
<div class="kpi r"><div class="kpi-n">${fmtN((d.critical_alerts||0)+(d.warning_alerts||0))}</div><div class="kpi-l">⚠ Alarmi</div></div>`;
$('dash-top-title').textContent='Top 10 Saveza';
if(d.top_savezi) $('dash-top-body').innerHTML=`<table class="dt"><thead><tr><th>Savez</th><th>Klubova</th><th style="text-align:right">Sportaša</th></tr></thead><tbody>${d.top_savezi.slice(0,8).map(s=>`<tr onclick="G('savezi')"><td style="font-weight:600">${s.naziv}</td><td class="mn">${fmtN(s.klubova_clanica)}</td><td class="ev">${fmtN(s.registriranih)}</td></tr>`).join('')}</tbody></table>`;
}
}catch(e){console.error('dash',e)}
}
async function loadKlubDashKpi(){
if(!_klubId) return;
try{
const [cl,lij]=await Promise.all([
fetch(`${API}/klub/${_klubId}/clanarine`).then(r=>r.json()),
fetch(`${API}/klub/${_klubId}/lijecnicki`).then(r=>r.json())
]);
if($('dk-cl')) $('dk-cl').textContent=fmtN(cl.count);
if($('dk-lij')) $('dk-lij').textContent=lij.istekli+lij.uskoro;
if($('dk-dug')) $('dk-dug').textContent=fmtE(cl.dug);
if(lij.istekli>0) $('dash-alerts').innerHTML+=`<div class="alert-bar"><div class="alert-ico">🏥</div><div class="alert-txt"><strong>${lij.istekli}</strong> sportaša ima istekao liječnički pregled. <a href="#" onclick="G('k-lijecnicki')" style="color:var(--red)">→ Pregledaj</a></div></div>`;
if(lij.uskoro>0) $('dash-alerts').innerHTML+=`<div class="alert-bar warn"><div class="alert-ico">🔔</div><div class="alert-txt"><strong>${lij.uskoro}</strong> sportaša ima pregled koji uskoro ističe (30 dana).</div></div>`;
$('nb-clan').textContent=cl.count;
$('nb-clan-dug').textContent=cl.dug>0?'!':'0';
$('nb-lij-alert').textContent=lij.istekli+lij.uskoro;
}catch(e){console.error('klub kpi',e)}
}
// ── SAVEZI ───────────────────────────────────────────────────────
async function loadSaveziPlatform(){
const q=$('sv-q')?.value||'';
$('sv-grid').innerHTML='<div class="ld" style="grid-column:1/-1"><div class="sp"></div></div>';
try{
const d=await fetch(`${API}/savezi?limit=250${q?'&q='+encodeURIComponent(q):''}`).then(r=>r.json());
const rows=d.rows||d;
$('nb-savezi').textContent=rows.length;
if(!window._svSportsBuilt){window._svSportsBuilt=true;const sp=[...new Set(rows.map(r=>r.sport).filter(Boolean))].sort();$('sv-sport').innerHTML='<option value="">Svi sportovi</option>'+sp.map(s=>`<option>${s}</option>`).join('');}
const sport=$('sv-sport')?.value||'';
const filtered=rows.filter(r=>(!sport||r.sport===sport)&&(!q||r.naziv?.toLowerCase().includes(q.toLowerCase())));
$('sv-grid').innerHTML=filtered.map(s=>buildSavezCard(s)).join('')||'<div class="empty" style="grid-column:1/-1">Nema saveza</div>';
}catch(e){$('sv-grid').innerHTML='<div class="empty" style="grid-column:1/-1">Greška: '+e.message+'</div>'}
}
function buildSavezCard(s){
return `<div class="cd" style="cursor:pointer;transition:border-color .2s" onmouseenter="this.style.borderColor='var(--pgz-blue2)'" onmouseleave="this.style.borderColor='var(--rim)'" onclick="openSavezDetail(${s.id})">
<div class="cd-h"><div><div style="font-size:11px;font-weight:700;color:var(--t0)">${s.naziv||''}</div><div style="font-size:8px;color:var(--pgz-gold);font-family:var(--mono);margin-top:2px">${(s.sport||'svesportski').toUpperCase()}</div></div></div>
<div class="cd-b" style="padding:8px 12px">
<div class="srow"><label>Predsjednik</label><span style="color:var(--cyan);font-size:10px">${s.predsjednik||''}</span></div>
<div class="srow"><label>Tajnik</label><span style="color:${s.tajnik?'var(--t1)':'var(--red)'};font-size:10px">${s.tajnik||'NULL'}</span></div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:4px;margin-top:8px">
<div style="text-align:center;background:var(--bg3);border-radius:3px;padding:5px"><div style="font-family:var(--mono);font-size:13px;color:var(--pgz-gold)">${fmtN(s.klubova_clanica||0)}</div><div style="font-size:7px;color:var(--t4)">Klubova</div></div>
<div style="text-align:center;background:var(--bg3);border-radius:3px;padding:5px"><div style="font-family:var(--mono);font-size:13px;color:var(--green)">${fmtN(s.registriranih||0)}</div><div style="font-size:7px;color:var(--t4)">Sportaša</div></div>
<div style="text-align:center;background:var(--bg3);border-radius:3px;padding:5px"><div style="font-family:var(--mono);font-size:13px;color:var(--cyan)">${fmtN(s.trenera||0)}</div><div style="font-size:7px;color:var(--t4)">Trenera</div></div>
</div>
</div>
</div>`;
}
async function openSavezDetail(id){
openPanel(`Učitavam savez...`);
try{
const d=await fetch(`${API}/savezi/${id}/full`).then(r=>r.json());
$('panel-hdr-t').textContent=d.naziv||'Savez';
$('panel-body').innerHTML=`
<div class="cd-b" style="background:var(--bg3);border-radius:var(--r);margin-bottom:12px">
<div class="srow"><label>Sport</label><span>${d.sport||''}</span></div>
<div class="srow"><label>Predsjednik</label><span style="color:var(--cyan)">${d.predsjednik||''}</span></div>
<div class="srow"><label>Tajnik</label><span style="color:${d.tajnik?'var(--t1)':'var(--red)'}">${d.tajnik||'NULL'}</span></div>
<div class="srow"><label>OIB</label><span class="mn">${d.oib?formatOib(d.oib,{savez_id:d.id}):''}</span></div>
<div class="srow"><label>Email</label><span>${d.email||''}</span></div>
</div>
<div class="tabs" id="sv-tabs">
<div class="tab on" onclick="showTab('sv-tabs','sv-t-klubovi')">🏟 Klubovi (${(d.klubovi||[]).length})</div>
<div class="tab" onclick="showTab('sv-tabs','sv-t-clanovi')">👤 Sportaši (${(d.clanovi||[]).length})</div>
<div class="tab" onclick="showTab('sv-tabs','sv-t-treneri')">📋 Treneri (${(d.treneri||[]).length})</div>
</div>
<div class="tab-c on" id="sv-t-klubovi">
<table class="dt"><thead><tr><th>Naziv</th><th>Grad</th><th>Predsjednik</th><th>NK</th><th>Čl.</th></tr></thead>
<tbody>${(d.klubovi||[]).map(k=>`<tr onclick="openKlubDetail(${k.id})" style="cursor:pointer">
<td style="font-weight:600;color:var(--t0)">${k.naziv||''}</td>
<td>${k.grad||''}</td>
<td style="color:var(--cyan);font-size:10px">${k.predsjednik||''}</td>
<td>${k.nositelj_kvalitete?'<span class="bk bk-gld">NK</span>':''}</td>
<td class="mn">${fmtN(k.broj_clanova||0)}</td>
</tr>`).join('')||'<tr><td colspan="5" class="empty">Nema klubova</td></tr>'}</tbody>
</table>
</div>
<div class="tab-c" id="sv-t-clanovi">
<div class="g2">${(d.clanovi||[]).slice(0,20).map(c=>buildMiniSportasCard(c)).join('')||'<div class="empty">Nema sportaša za ovaj savez</div>'}</div>
</div>
<div class="tab-c" id="sv-t-treneri">
${(d.treneri||[]).length>0?
'<table class="dt"><thead><tr><th>Ime</th><th>Licenca</th><th>Sport</th><th>Aktivan</th></tr></thead><tbody>'+(d.treneri||[]).map(t=>`<tr><td style="font-weight:600">${t.ime||''} ${t.prezime||''}</td><td class="mn">${t.licenca_broj||''}</td><td>${t.sport||''}</td><td>${t.aktivan?'<span class="bk bk-g">DA</span>':'<span class="bk bk-r">NE</span>'}</td></tr>`).join('')+'</tbody></table>':
'<div class="empty">Nema trenera za ovaj savez</div>'}
</div>`;
}catch(e){$('panel-body').innerHTML='<div class="empty">Greška: '+e.message+'</div>'}
}
// ── KLUBOVI ───────────────────────────────────────────────────────
async function loadKluboviPlatform(){
const q=$('kl-q')?.value||'';
const sport=$('kl-sport2')?.value||'';
const grad=$('kl-grad2')?.value||'';
const nk=$('kl-nk2')?.checked;
let url=`${API}/klubovi?limit=100`;
if(q) url+=`&q=${encodeURIComponent(q)}`;
if(sport) url+=`&sport=${encodeURIComponent(sport)}`;
if(grad) url+=`&grad=${encodeURIComponent(grad)}`;
if(nk) url+=`&nositelj=true`;
$('kl-cards2').innerHTML='<div class="ld" style="grid-column:1/-1"><div class="sp"></div></div>';
try{
const d=await fetch(url).then(r=>r.json());
const rows=(d.rows||d).filter(k=>{const n=k.naziv||k.klub||'';return n&&!/^https?:\/\//i.test(n)&&!/@/.test(n)&&!/^\d{4,5}\s/.test(n)&&!/seminar|webinar/i.test(n)});
if(!window._klFiltersBuilt2){window._klFiltersBuilt2=true;
const sp=[...new Set(rows.map(r=>r.sport).filter(Boolean))].sort();$('kl-sport2').innerHTML='<option value="">Svi sportovi</option>'+sp.map(s=>`<option>${s}</option>`).join('');
fetch(`${API}/gradovi`).then(r=>r.json()).then(gr=>{$('kl-grad2').innerHTML='<option value="">Svi gradovi</option>'+(Array.isArray(gr)?gr:[]).map(g=>`<option>${g}</option>`).join('');}).catch(()=>{});
}
if(_klView2==='card'){
$('kl-cards2').innerHTML=rows.map(k=>buildKlubCard2(k)).join('')||'<div class="empty" style="grid-column:1/-1">Nema klubova</div>';
} else {
$('kl-dt').innerHTML=rows.map(k=>`<tr onclick="openKlubDetail(${k.id})" style="cursor:pointer">
<td style="font-weight:600">${k.naziv||k.klub||''}</td>
<td>${k.sport||''}</td><td>${k.grad||''}</td>
<td style="color:var(--cyan);font-size:10px">${k.predsjednik||''}</td>
<td>${k.nositelj_kvalitete?'<span class="bk bk-gld">NK</span>':''}</td>
<td class="ev">${fmtN(k.broj_clanova||0)}</td>
<td><button class="btn btn-sm" onclick="event.stopPropagation();openKlubDetail(${k.id})">→</button></td>
</tr>`).join('');
}
}catch(e){$('kl-cards2').innerHTML='<div class="empty" style="grid-column:1/-1">Greška</div>'}
}
let _klView2='card';
function setKlView(v){_klView2=v;['c','t'].forEach(x=>{$('kl-vt-'+x)?.classList.toggle('on',x===v[0])});$('kl-cards2').style.display=v==='card'?'grid':'none';$('kl-table2').style.display=v==='table'?'block':'none';loadKluboviPlatform();}
function buildKlubCard2(k){
const score=(k.ima_predsjednika?25:0)+(k.ima_oib?25:0)+(k.ima_sjediste?25:0)+(k.nositelj_kvalitete?25:0);
return `<div class="cd" style="cursor:pointer;transition:border-color .2s" onmouseenter="this.style.borderColor='var(--pgz-blue2)'" onmouseleave="this.style.borderColor='var(--rim)'" onclick="openKlubDetail(${k.id})">
<div class="cd-h">
<div style="min-width:0"><div style="font-size:11px;font-weight:700;color:var(--t0);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${k.naziv||k.klub||''}</div><div style="font-size:8px;color:var(--t2);font-family:var(--mono)">${k.sport||''} · ${k.grad||''}</div></div>
${k.nositelj_kvalitete?'<span class="bk bk-gld">NK</span>':''}
</div>
<div class="cd-b" style="padding:8px 12px">
<div class="srow"><label>Predsjednik</label><span style="color:var(--cyan);font-size:10px">${k.predsjednik||''}</span></div>
<div class="srow"><label>Savez</label><span style="font-size:10px">${k.savez||''}</span></div>
<div style="display:flex;justify-content:space-between;margin-top:8px">
<div style="text-align:center"><div style="font-family:var(--mono);font-size:12px;color:var(--pgz-gold)">${fmtN(k.broj_clanova||0)}</div><div style="font-size:7px;color:var(--t4)">Čl.</div></div>
<div style="text-align:center"><div style="font-family:var(--mono);font-size:12px;color:var(--green)">${fmtN(k.registriranih||0)}</div><div style="font-size:7px;color:var(--t4)">Reg.</div></div>
<div style="text-align:center"><div style="font-family:var(--mono);font-size:12px;color:${score>=50?'var(--green)':'var(--red)'}">${score}%</div><div style="font-size:7px;color:var(--t4)">Data</div></div>
</div>
<div style="display:flex;gap:3px;margin-top:8px;border-top:1px solid var(--rim);padding-top:7px">
<button class="btn btn-sm" style="flex:1;font-size:9px" onclick="event.stopPropagation();openKlubDetail(${k.id})">👁</button>
<button class="btn btn-sm" style="flex:1;font-size:9px" onclick="event.stopPropagation();openKlubClanovi(${k.id})">👥</button>
<button class="btn btn-sm btn-red" style="flex:1;font-size:9px" onclick="event.stopPropagation();openKlubLijecnicki(${k.id})">🏥</button>
</div>
</div>
</div>`;
}
async function openKlubDetail(id){
openPanel('Učitavam klub...');
try{
const k=await fetch(`${API}/klubovi/${id}`).then(r=>r.json());
$('panel-hdr-t').textContent=k.naziv||'Klub';
const clanovi=k.clanovi||[];
$('panel-body').innerHTML=`
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:12px">
<div style="background:var(--bg3);border-radius:3px;padding:8px;text-align:center"><div style="font-family:var(--mono);font-size:18px;color:var(--pgz-gold)">${fmtN(k.stats?.broj_clanova||clanovi.length||0)}</div><div style="font-size:8px;color:var(--t4)">Članova</div></div>
<div style="background:var(--bg3);border-radius:3px;padding:8px;text-align:center"><div style="font-family:var(--mono);font-size:18px;color:${k.stats?.lijecnicki_istekli>0?'var(--red)':'var(--green)'}">${k.stats?.lijecnicki_istekli||0}</div><div style="font-size:8px;color:var(--t4)">Istekli pregledi</div></div>
</div>
<div style="background:var(--bg3);border-radius:var(--r);padding:10px;margin-bottom:12px">
<div class="srow"><label>Predsjednik</label><span style="color:var(--cyan)">${k.predsjednik||''}</span></div>
<div class="srow"><label>Tajnik</label><span style="color:${k.tajnik?'var(--t1)':'var(--red)'}">${k.tajnik||'NULL'}</span></div>
<div class="srow"><label>Savez</label><span>${k.savez_naziv||k.savez||''}</span></div>
<div class="srow"><label>OIB</label><span class="mn">${k.oib?formatOib(k.oib,{klub_id:k.id,savez_id:k.savez_id}):''}</span></div>
<div class="srow"><label>Sjedište</label><span style="font-size:10px">${k.sjediste||''}</span></div>
<div class="srow"><label>Razina</label><span>${k.razina||''}</span></div>
</div>
<div class="tabs" id="kl-dtabs">
<div class="tab on" onclick="showTab('kl-dtabs','kl-dt-clanovi')">👤 Sportaši (${clanovi.length})</div>
<div class="tab" onclick="showTab('kl-dtabs','kl-dt-potpore')">€ Potpore (${(k.potpore||[]).length})</div>
<div class="tab" onclick="showTab('kl-dtabs','kl-dt-doc')">📄 Info</div>
</div>
<div class="tab-c on" id="kl-dt-clanovi">
<div class="g2">${clanovi.slice(0,16).map(c=>buildMiniSportasCard(c)).join('')||'<div class="empty">Nema članova u sustavu</div>'}</div>
${clanovi.length>16?`<div style="text-align:center;padding:8px;font-size:10px;color:var(--t4)">+${clanovi.length-16} sportaša</div>`:''}
</div>
<div class="tab-c" id="kl-dt-potpore">
${(k.potpore||[]).length>0?'<table class="dt"><thead><tr><th>Naziv</th><th>Godina</th><th style="text-align:right">Iznos</th></tr></thead><tbody>'+(k.potpore||[]).map(p=>`<tr><td>${p.naziv_kluba||''}</td><td class="mn">${p.godina||''}</td><td class="ev">${fmtE(p.iznos)}</td></tr>`).join('')+'</tbody></table>':'<div class="empty">Nema evidentiranih potpora</div>'}
</div>
<div class="tab-c" id="kl-dt-doc">
${k.opis_djelatnosti?'<div style="font-size:10px;color:var(--t2);line-height:1.7;max-height:200px;overflow:auto">'+k.opis_djelatnosti+'</div>':''}
</div>`;
}catch(e){$('panel-body').innerHTML='<div class="empty">Greška: '+e.message+'</div>'}
}
// ── SPORTAŠI ─────────────────────────────────────────────────────
function buildMiniSportasCard(c){
const hoo=['','I','II','III','IV','V','VI'][c.hoo_kategorija||0]||'';
return `<div class="cd" style="cursor:pointer;transition:all .15s" onmouseenter="this.style.borderColor='var(--pgz-blue2)'" onmouseleave="this.style.borderColor='var(--rim)'" onclick="openSportasProfil(${c.id})">
<div class="cd-b" style="padding:10px;display:flex;gap:9px;align-items:center">
<div style="width:38px;height:38px;border-radius:5px;background:var(--bg3);border:1px solid var(--rim2);overflow:hidden;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0">${c.slika_url?`<img src="${c.slika_url}" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none'">`:c.spol==='Ž'?'👩':'👤'}</div>
<div style="min-width:0">
<div style="font-size:11px;font-weight:600;color:var(--t0)">${c.ime||''} ${c.prezime||''}</div>
<div style="font-size:8px;color:var(--t2);font-family:var(--mono)">${c.pozicija||c.uloga||''}</div>
<div style="display:flex;gap:3px;margin-top:3px">
${hoo?`<span class="bk bk-gld" style="font-size:6px">HOO ${hoo}</span>`:''}
${c.reprezentativac?'<span class="bk bk-r" style="font-size:6px">Repr.</span>':''}
</div>
</div>
</div>
</div>`;
}
async function loadSportasiPlatform(){
const q=$('sp-q2')?.value||'';
const hoo=$('sp-hoo2')?.value||'';
const repr=$('sp-repr2')?.checked;
let url=`${API}/clanovi-full?limit=80`;
if(q) url+=`&q=${encodeURIComponent(q)}`;
if(hoo) url+=`&hoo=${hoo}`;
if(repr) url+=`&reprezentativac=true`;
$('sp-grid').innerHTML='<div class="ld" style="grid-column:1/-1"><div class="sp"></div></div>';
try{
const d=await fetch(url).then(r=>r.json());
const rows=d.rows||d;
$('sp-grid').innerHTML=rows.map(c=>buildMiniSportasCard(c)).join('')||'<div class="empty" style="grid-column:1/-1">Nema sportaša</div>';
}catch(e){$('sp-grid').innerHTML='<div class="empty" style="grid-column:1/-1">Greška</div>'}
}
async function openSportasProfil(id){
openPanel('Učitavam profil…');
try{
const c=await fetch(`${API}/sportas/${id}/profil`).then(r=>r.json());
const st=c.stats||{};
const hoo=['','I','II','III','IV','V','VI'][c.hoo_kategorija||0]||'';
$('panel-hdr-t').textContent=`${c.ime||''} ${c.prezime||''}`;
$('panel-body').innerHTML=`
<div class="sportas-hero">
<div class="sh-ava">${c.slika_url?`<img src="${c.slika_url}" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none'">`:c.spol==='Ž'?'👩':'👤'}</div>
<div class="sh-info">
<div class="sh-name">${c.ime||''} ${c.prezime||''}</div>
<div class="sh-sub">${c.pozicija||c.uloga||''} · ${c.sport||''} · ${c.klub_naziv_godisnjak||c.klub_naziv_full||''}</div>
<div class="sh-badges">
${hoo!==''?`<span class="bk bk-gld">HOO ${hoo}</span>`:''}
${c.reprezentativac?'<span class="bk bk-r">🏅 Reprezentativac</span>':''}
${c.kategoriziran?'<span class="bk bk-g">Kategoriziran</span>':''}
${c.stipendiran?'<span class="bk bk-a">Stipendiran</span>':''}
${c.aktivan?'<span class="bk bk-g">Aktivan</span>':'<span class="bk bk-r">Neaktivan</span>'}
</div>
<div class="sh-stats">
<div class="sh-stat"><div class="sh-stat-n">${st.ukupno_nastupa||0}</div><div class="sh-stat-l">Nastupi</div></div>
<div class="sh-stat"><div class="sh-stat-n" style="color:var(--green)">${st.ukupno_pogodaka||0}</div><div class="sh-stat-l">Golovi</div></div>
<div class="sh-stat"><div class="sh-stat-n" style="color:var(--cyan)">${st.ukupno_asistencija||0}</div><div class="sh-stat-l">Asist.</div></div>
<div class="sh-stat"><div class="sh-stat-n" style="color:var(--amber)">${st.ukupno_zutih||0}</div><div class="sh-stat-l">Žuti</div></div>
<div class="sh-stat"><div class="sh-stat-n" style="color:var(--red)">${st.ukupno_crvenih||0}</div><div class="sh-stat-l">Crveni</div></div>
<div class="sh-stat"><div class="sh-stat-n">${st.ukupno_minuta||0}</div><div class="sh-stat-l">Minuta</div></div>
</div>
</div>
</div>
<div class="tabs" id="sp-tabs">
<div class="tab on" onclick="showTab('sp-tabs','sp-t-bio')">📋 Bio</div>
<div class="tab" onclick="showTab('sp-tabs','sp-t-sezona')">📊 Sezone (${(c.clan_sezona||[]).length})</div>
<div class="tab" onclick="showTab('sp-tabs','sp-t-utakmice')">⚽ Utakmice (${(c.utakmice||[]).length})</div>
<div class="tab" onclick="showTab('sp-tabs','sp-t-god')">📅 Godišnjaci</div>
</div>
<div class="tab-c on" id="sp-t-bio">
<div style="background:var(--bg3);border-radius:var(--r);padding:10px">
<div class="srow"><label>OIB</label><span class="mn">${c.oib?formatOib(c.oib,{klub_id:c.klub_id,savez_id:c.savez_id}):''}</span></div>
<div class="srow"><label>Datum rodjenja</label><span>${c.datum_rodenja||c.datum_rodjenja||''}</span></div>
<div class="srow"><label>Spol</label><span>${c.spol||''}</span></div>
<div class="srow"><label>Visina/Težina</label><span>${c.visina_cm||''} cm / ${c.tezina_kg||''} kg</span></div>
<div class="srow"><label>Dres</label><span class="mn">${c.broj_dresa||c.broj_dres||''}</span></div>
<div class="srow"><label>Dominantna noga</label><span>${c.dominantna_noga||''}</span></div>
<div class="srow"><label>HOO vrijedi do</label><span>${c.hoo_vrijedi_do||c.hoo_kat_vrijedi_do||''}</span></div>
</div>
${c.napomena?`<div style="margin-top:8px;background:var(--bg3);border-radius:3px;padding:10px;font-size:10px;color:var(--t2);line-height:1.7">${c.napomena}</div>`:''}
</div>
<div class="tab-c" id="sp-t-sezona">
${(c.clan_sezona||[]).length>0?`
<div class="sezona-hdr sezona-row"><span>Sezona</span><span>Natjecanje</span><span>Nas.</span><span>Gol</span><span>Ast.</span><span>Žuti</span><span>Crv.</span><span>Min.</span></div>
${(c.clan_sezona||[]).map(s=>`<div class="sezona-row" onclick="s.natjecanje_url&&window.open(s.natjecanje_url,'_blank')" style="${s.natjecanje_url?'cursor:pointer':''}">
<span class="mn">${s.sezona||''}</span>
<span style="font-size:10px;color:var(--t2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${s.natjecanje||''}">${s.natjecanje||''}</span>
<span class="mn" style="color:var(--pgz-gold)">${s.nastupi||0}</span>
<span class="mn" style="color:var(--green)">${s.pogoci||0}</span>
<span class="mn" style="color:var(--cyan)">${s.asistencije||0}</span>
<span class="mn" style="color:var(--amber)">${s.zuti_kartoni||0}</span>
<span class="mn" style="color:var(--red)">${s.crveni_kartoni||0}</span>
<span class="mn">${s.minute_total||''}</span>
</div>`).join('')}`:'<div class="empty">Nema podataka o sezonama</div>'}
</div>
<div class="tab-c" id="sp-t-utakmice">
${(c.utakmice||[]).length>0?`
<table class="dt"><thead><tr><th>Datum</th><th>Utakmica</th><th>Rezult.</th><th>Gol</th><th>Žuti</th><th>Min.</th></tr></thead>
<tbody>${(c.utakmice||[]).map(u=>`<tr onclick="window.open('${u.source_url||'#'}','_blank')" style="cursor:pointer">
<td class="mn" style="font-size:10px">${u.datum||''}</td>
<td style="font-size:10px">
<div style="display:flex;align-items:center;gap:5px">
${u.klub_dom_logo?`<img src="${u.klub_dom_logo}" style="width:16px;height:16px;object-fit:contain">`:''} ${u.klub_dom||''} vs ${u.klub_gost||''} ${u.klub_gost_logo?`<img src="${u.klub_gost_logo}" style="width:16px;height:16px;object-fit:contain">`:''}
</div>
<div style="font-size:9px;color:var(--t4)">${u.natjecanje||''}</div>
</td>
<td class="mn" style="color:var(--pgz-gold)">${u.rezultat||''}</td>
<td class="mn" style="color:var(--green)">${u.pogodaka||0}</td>
<td class="mn" style="color:var(--amber)">${u.zuti_kartoni||0}</td>
<td class="mn">${u.minute||''}'</td>
</tr>`).join('')}</tbody></table>`:'<div class="empty">Nema podataka o utakmicama</div>'}
</div>
<div class="tab-c" id="sp-t-god">
<div style="background:var(--bg3);border-radius:var(--r);padding:12px">
<div style="font-size:8px;color:var(--t4);font-family:var(--mono);letter-spacing:.5px;margin-bottom:8px">PRISUTNOST U GODIŠNJACIMA (${(c.godisnjak_godine||[]).length} godina)</div>
<div style="display:flex;flex-wrap:wrap;gap:4px">${(c.godisnjak_godine||[]).map(g=>`<span class="bk bk-b">${g}</span>`).join('')||''}</div>
${c.godisnjak_prvi?`<div style="font-size:9px;color:var(--t2);margin-top:8px;font-family:var(--mono)">Prvo: ${c.godisnjak_prvi} · Zadnje: ${c.godisnjak_zadnji}</div>`:''}
</div>
</div>`;
}catch(e){$('panel-body').innerHTML='<div class="empty">Greška: '+e.message+'</div>'}
}
// ── KLUB ERP: ČLANOVI ────────────────────────────────────────────
let _clView='list';
function setClView(v){_clView=v;['l','c'].forEach(x=>{$('kcl-vt-'+x)?.classList.toggle('on',x===v[0])});$('kcl-list').style.display=v==='list'?'block':'none';$('kcl-cards').style.display=v==='card'?'grid':'none';loadKlubClanovi();}
async function loadKlubClanovi(){
const kid=_klubId;
if(!kid){$('kcl-list').innerHTML='<div class="empty">Nema klub ID — prijavite se kao klub admin</div>';return}
const q=$('kcl-q')?.value||'';
let url=`${API}/clanovi-full?klub_id=${kid}&limit=200`;
if(q) url+=`&q=${encodeURIComponent(q)}`;
$('kcl-list').innerHTML='<div class="ld"><div class="sp"></div></div>';
try{
const d=await fetch(url).then(r=>r.json());
const rows=d.rows||d;
$('nb-clan').textContent=rows.length;
if(_clView==='list'){
$('kcl-list').innerHTML=rows.map(c=>`
<div class="clan-card" onclick="openSportasProfil(${c.id})">
<div class="clan-ava">${c.slika_url?`<img src="${c.slika_url}" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none'">`:c.spol==='Ž'?'👩':'👤'}</div>
<div class="clan-info">
<div class="ci-name">${c.ime||''} ${c.prezime||''}</div>
<div class="ci-meta">${c.sport||''} · ${c.pozicija||c.uloga||''} · ${c.godina_rodenja||'g.r. '}</div>
<div class="ci-status">
${c.aktivan?'<span class="bk bk-g">Aktivan</span>':'<span class="bk bk-r">Neaktivan</span>'}
${c.representativac?'<span class="bk bk-r">Repr.</span>':''}
${c.hoo_kategorija?`<span class="bk bk-gld">HOO ${['','I','II','III','IV','V','VI'][c.hoo_kategorija||0]}</span>`:''}
</div>
</div>
<div style="display:flex;flex-direction:column;gap:4px;align-items:flex-end">
<button class="btn btn-sm" onclick="event.stopPropagation();openSportasProfil(${c.id})">👁</button>
<button class="btn btn-sm" onclick="event.stopPropagation()">✎</button>
</div>
</div>`).join('')||'<div class="empty">Nema članova</div>';
}
}catch(e){$('kcl-list').innerHTML='<div class="empty">Greška: '+e.message+'</div>'}
}
// ── KLUB ERP: ČLANARINE ──────────────────────────────────────────
async function loadClanarine(){
const kid=_klubId;
if(!kid){$('clar-tbody').innerHTML='<tr><td colspan="8" class="empty">Nema klub ID</td></tr>';return}
const god=$('clar-god')?.value||'';
const status=$('clar-status')?.value||'';
let url=`${API}/klub/${kid}/clanarine`;
const params=[]; if(god) params.push(`godina=${god}`); if(status) params.push(`status=${status}`);
if(params.length) url+='?'+params.join('&');
$('clar-tbody').innerHTML='<tr><td colspan="8"><div class="ld"><div class="sp"></div></div></td></tr>';
try{
const d=await fetch(url).then(r=>r.json());
const rows=d.rows||[];
$('clar-summary').innerHTML=`
<div class="kpi g"><div class="kpi-n">${fmtE(d.naplaceno)}</div><div class="kpi-l">Naplaćeno</div></div>
<div class="kpi r"><div class="kpi-n">${fmtE(d.dug)}</div><div class="kpi-l">Ukupni dug</div></div>
<div class="kpi b"><div class="kpi-n">${fmtN(d.count)}</div><div class="kpi-l">Zapisa</div></div>`;
const q=($('clar-q')?.value||'').toLowerCase();
const filtered=rows.filter(r=>!q||((r.ime||'')+(r.prezime||'')).toLowerCase().includes(q));
if(!window._clarFiltersBuilt){window._clarFiltersBuilt=true;
const years=[...new Set(rows.map(r=>r.godina).filter(Boolean))].sort((a,b)=>b-a);
$('clar-god').innerHTML='<option value="">Sve godine</option>'+years.map(y=>`<option>${y}</option>`).join('');}
$('clar-tbody').innerHTML=filtered.map(r=>{
const dug=parseFloat(r.iznos_propisan||0)-parseFloat(r.iznos_placen||0);
const st=r.status||'neplaceno';
const stBk=st==='placeno'?'bk-g':st==='djelomicno'?'bk-a':'bk-r';
return `<tr onclick="openSportasProfil(${r.clan_id})" style="cursor:pointer">
<td style="display:flex;align-items:center;gap:7px">
<div style="width:28px;height:28px;border-radius:4px;background:var(--bg3);overflow:hidden;display:flex;align-items:center;justify-content:center;font-size:12px">${r.slika_url?`<img src="${r.slika_url}" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none'">`:r.spol==='Ž'?'👩':'👤'}</div>
<div><div style="font-weight:600">${r.ime||''} ${r.prezime||''}</div><div style="font-size:8px;color:var(--t4);font-family:var(--mono)">${r.oib?formatOib(r.oib,{klub_id:r.klub_id,savez_id:r.savez_id}):''}</div></div>
</td>
<td style="font-size:10px;color:var(--t2)">${['','I','II','III','IV','V','VI'][r.hoo_kategorija||0]||''}</td>
<td class="mn">${r.godina||''}</td>
<td class="ev">${fmtE(r.iznos_propisan)}</td>
<td class="ev" style="color:var(--green)">${fmtE(r.iznos_placen)}</td>
<td class="ev" style="color:${dug>0?'var(--red)':'var(--green)'}">${fmtE(dug)}</td>
<td><span class="bk ${stBk}">${st}</span></td>
<td><button class="btn btn-sm" onclick="event.stopPropagation()">✎</button></td>
</tr>`;}).join('')||'<tr><td colspan="8" class="empty">Nema zapisa</td></tr>';
}catch(e){$('clar-tbody').innerHTML='<tr><td colspan="8" class="empty">Greška: '+e.message+'</td></tr>'}
}
// ── KLUB ERP: LIJEČNIČKI ─────────────────────────────────────────
async function loadLijecnicki(){
const kid=_klubId;
if(!kid){$('lij-tbody').innerHTML='<tr><td colspan="8" class="empty">Nema klub ID</td></tr>';return}
const st=$('lij-status')?.value||'';
const q=$('lij-q')?.value||'';
let url=`${API}/klub/${kid}/lijecnicki`;
$('lij-tbody').innerHTML='<tr><td colspan="8"><div class="ld"><div class="sp"></div></div></td></tr>';
try{
const d=await fetch(url).then(r=>r.json());
const rows=d.rows||[];
$('nb-lij-alert').textContent=d.istekli+d.uskoro;
$('lij-alerts').innerHTML='';
if(d.istekli>0) $('lij-alerts').innerHTML+=`<div class="alert-bar"><div class="alert-ico">🏥</div><div class="alert-txt"><strong>${d.istekli}</strong> sportaša ima ISTEKAO liječnički pregled. Hitna obnova!</div></div>`;
if(d.uskoro>0) $('lij-alerts').innerHTML+=`<div class="alert-bar warn"><div class="alert-ico">🔔</div><div class="alert-txt"><strong>${d.uskoro}</strong> sportaša ima pregled koji ističe unutar 30 dana.</div></div>`;
const filtered=rows.filter(r=>(!st||r.status_pregled===st)&&(!q||((r.ime||'')+(r.prezime||'')).toLowerCase().includes(q.toLowerCase())));
$('lij-tbody').innerHTML=filtered.map(r=>{
const stBk={validan:'bk-g',uskoro_istece:'bk-a',istekao:'bk-r',nepoznato:'bk-b'}[r.status_pregled]||'bk-b';
const vrijDo=r.vrijedi_do?new Date(r.vrijedi_do).toLocaleDateString('hr-HR'):'';
const datPr=r.datum_pregleda?new Date(r.datum_pregleda).toLocaleDateString('hr-HR'):'';
return `<tr onclick="openSportasProfil(${r.clan_id})" style="cursor:pointer">
<td style="display:flex;align-items:center;gap:7px">
<div style="width:28px;height:28px;border-radius:4px;background:var(--bg3);overflow:hidden;display:flex;align-items:center;justify-content:center;font-size:12px">${r.slika_url?`<img src="${r.slika_url}" style="width:100%;height:100%;object-fit:cover" onerror="this.style.display='none'">`:r.spol==='Ž'?'👩':'👤'}</div>
<div style="font-weight:600">${r.ime||''} ${r.prezime||''}</div>
</td>
<td class="mn" style="font-size:10px">${datPr}</td>
<td class="mn" style="font-size:10px;color:${r.status_pregled==='istekao'?'var(--red)':r.status_pregled==='uskoro_istece'?'var(--amber)':'var(--green)'}">${vrijDo}</td>
<td><span class="bk ${stBk}">${{validan:'✓ Validan',uskoro_istece:'⏰ Uskoro',istekao:'❌ Istekao',nepoznato:'? Nepoznato'}[r.status_pregled]||''}</span></td>
<td style="font-size:10px;color:var(--t2)">${r.ustanova||''}</td>
<td class="mn">${r.iznos_zzjz?fmtE(r.iznos_zzjz):''}</td>
<td>${r.nalaz?'<button class="btn btn-sm btn-pgz" onclick="event.stopPropagation()">📄 Nalaz</button>':''}</td>
<td><button class="btn btn-sm" onclick="event.stopPropagation()">✎</button></td>
</tr>`;}).join('')||'<tr><td colspan="8" class="empty">Nema liječničkih pregleda</td></tr>';
}catch(e){$('lij-tbody').innerHTML='<tr><td colspan="8" class="empty">Greška: '+e.message+'</td></tr>'}
}
// ── FINANCIJE ────────────────────────────────────────────────────
let _finChart=null;
async function loadFinancije(){
const god=$('fin-god')?.value||'';
const sport=$('fin-sport')?.value||'';
const q=$('fin-q')?.value||'';
let url=`${API}/sufinanciranje?limit=500`;
if(god) url+=`&godina=${god}`;
if(sport) url+=`&sport=${encodeURIComponent(sport)}`;
if(q) url+=`&q=${encodeURIComponent(q)}`;
try{
const d=await fetch(url).then(r=>r.json());
const rows=d.rows||[];
const total=d.total||0;
if(!window._finFiltersBuilt){window._finFiltersBuilt=true;
const yrs=(d.years||[]).sort((a,b)=>b-a);$('fin-god').innerHTML='<option value="">Sve godine</option>'+yrs.map(y=>`<option>${y}</option>`).join('');
const sp=[...new Set(rows.map(r=>r.sport).filter(Boolean))].sort();$('fin-sport').innerHTML='<option value="">Svi sportovi</option>'+sp.map(s=>`<option>${s}</option>`).join('');}
$('fin-kpi').innerHTML=`
<div class="kpi b"><div class="kpi-n">${fmtE(total)}</div><div class="kpi-l">Ukupno</div></div>
<div class="kpi g"><div class="kpi-n">${fmtN(rows.filter(r=>r.sport).length)}</div><div class="kpi-l">Klub korisnika</div></div>
<div class="kpi c"><div class="kpi-n">${[...new Set(rows.map(r=>r.sport).filter(Boolean))].length}</div><div class="kpi-l">Sportova</div></div>
<div class="kpi r"><div class="kpi-n">${[...new Set(rows.map(r=>r.razina).filter(Boolean))].length}</div><div class="kpi-l">Razina</div></div>`;
const bySport={};rows.filter(r=>r.sport).forEach(r=>bySport[r.sport]=(bySport[r.sport]||0)+(r.iznos_eur||0));
const top=Object.entries(bySport).sort((a,b)=>b[1]-a[1]).slice(0,10);
buildFinChart(top);
$('fin-top-tb').innerHTML=rows.slice(0,10).map(r=>`<tr onclick="r.source_url&&window.open(r.source_url,'_blank')" style="cursor:pointer"><td style="font-weight:600;font-size:11px">${r.korisnik||''}</td><td style="font-size:10px;color:var(--pgz-gold)">${r.sport||''}</td><td class="ev">${fmtE(r.iznos_eur)}</td></tr>`).join('');
$('fin-total').textContent=`Ukupno: ${fmtE(total)} · ${rows.length} zapisa`;
$('fin-tbody').innerHTML=rows.map(r=>`<tr>
<td style="font-weight:600;font-size:11px">${r.korisnik||''}</td>
<td style="font-size:10px;color:var(--pgz-gold)">${r.sport||''}</td>
<td><span class="bk bk-b" style="font-size:7px">${r.razina||''}</span></td>
<td class="mn">${r.godina||''}</td>
<td style="font-size:10px;color:var(--t2)">${r.vrsta||''}</td>
<td class="ev">${fmtE(r.iznos_eur)}</td>
<td>${r.source_url?`<a href="${r.source_url}" target="_blank" class="btn btn-sm" style="font-size:8px">↗</a>`:''}</td>
</tr>`).join('');
}catch(e){console.error('fin',e)}
}
function buildFinChart(data,type='bar'){
const c=$('fin-chart'); if(!c) return;
if(_finChart){_finChart.destroy();}
const colors=['#F4C430','#00c8e8','#00e88f','#7c5af0','#ff2d55','#f59e0b','#38bdf8','#a3e635','#fb923c','#e879f9'];
_finChart=new Chart(c.getContext('2d'),{type,data:{labels:data.map(([k])=>k),datasets:[{data:data.map(([,v])=>v),backgroundColor:type==='bar'?'rgba(0,76,196,.5)':colors,borderColor:type==='bar'?'rgba(0,76,196,.9)':colors,borderWidth:1}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:type!=='bar',labels:{color:'#8a95b4',font:{size:9}}},tooltip:{callbacks:{label:c=>'€'+fmtN(c.raw)}}},scales:type==='bar'?{x:{ticks:{color:'#4e5a7a',font:{size:9}}},y:{ticks:{color:'#4e5a7a',callback:v=>'€'+fmtN(v)}}}:{}}});
}
function switchFin(type){const c=$('fin-chart');if(!c||!_finChart)return;const data=_finChart.data;_finChart.destroy();const labels=data.labels;const vals=data.datasets[0].data;buildFinChart(labels.map((l,i)=>[l,vals[i]]),type);}
// ── NETWORK ISTRAŽIVAČ ───────────────────────────────────────────
async function searchNetwork(){
const osoba=$('net-osoba')?.value||'';
const klub=$('net-klub')?.value||'';
const entitet=$('net-entitet')?.value||'';
const q=osoba||klub||entitet;
if(!q){resetNetwork();return}
$('net-ld').style.display='flex';
try{
const d=await fetch(`${API}/network/pgz?q=${encodeURIComponent(q)}&max_nodes=150`).then(r=>r.json());
_netData=d; buildNetwork(d); $('net-stats').textContent=`${d.nodes.length} čvorova · ${d.edges.length} veza · Upit: "${q}"`;
}catch(e){$('net-ld').innerHTML='<div style="color:var(--red);font-size:10px">Greška: '+e.message+'</div>'}
}
async function resetNetwork(){
$('net-ld').style.display='flex';
$('net-ld').innerHTML='<div class="sp"></div><span style="font-family:var(--mono);font-size:9px">Inicijalizacija mreže…</span>';
try{
const d=await fetch(`${API}/network/pgz?max_nodes=100`).then(r=>r.json());
_netData=d; buildNetwork(d); $('net-stats').textContent=`${d.nodes.length} čvorova · ${d.edges.length} veza · Zadana mreža PGŽ`;
}catch(e){$('net-ld').innerHTML='<div style="color:var(--red);font-size:10px">Greška</div>'}
}
function expandNode(){if(_selectedNode){$('net-osoba').value=_selectedNode.label;searchNetwork();}}
function buildNetwork(data){
const wrap=$('net-wrap'); const W=wrap.offsetWidth, H=wrap.offsetHeight||520;
const svg=d3.select('#net-svg').attr('viewBox',`0 0 ${W} ${H}`);
svg.selectAll('*').remove();
const g=svg.append('g');
svg.call(d3.zoom().scaleExtent([.2,4]).on('zoom',e=>g.attr('transform',e.transform)));
const typeColor={person:'#00c8e8',club:'#00e88f',company:'#7c5af0',municipality:'#F4C430'};
const nodes=[...data.nodes], links=[...data.edges.map(e=>({...e}))];
const sim=d3.forceSimulation(nodes)
.force('link',d3.forceLink(links).id(d=>d.id).distance(70))
.force('charge',d3.forceManyBody().strength(-180))
.force('center',d3.forceCenter(W/2,H/2))
.force('collision',d3.forceCollide(16));
const link=g.append('g').selectAll('line').data(links).join('line')
.attr('stroke','rgba(30,42,80,.5)').attr('stroke-width',.7);
const node=g.append('g').selectAll('g').data(nodes).join('g')
.attr('cursor','pointer')
.call(d3.drag().on('start',ds).on('drag',dd).on('end',de))
.on('click',(ev,d)=>{
_selectedNode=d;
$('ni-type').textContent=d.type?.toUpperCase()+(d.forensic?' · ⚠ FORENZIČKI':'');
$('ni-name').textContent=d.label;
$('ni-detail').textContent=d.meta?.city||d.type||'PGŽ entitet';
$('ni-type').style.color=d.forensic?'var(--red)':'var(--t4)';
$('ni-name').style.color=d.forensic?'var(--red)':'var(--t0)';
$('net-info').style.display='block';
});
node.append('circle').attr('r',d=>d.forensic?10:d.type==='person'?6:7)
.attr('fill',d=>d.forensic?'#ff2d55':typeColor[d.type]||'#8a95b4')
.attr('stroke',d=>d.forensic?'rgba(255,45,85,.4)':'rgba(255,255,255,.1)')
.attr('stroke-width',d=>d.forensic?2:1).attr('opacity',.9);
node.filter(d=>d.forensic).append('circle').attr('r',16).attr('fill','none')
.attr('stroke','rgba(255,45,85,.3)').attr('stroke-width',1.5).attr('stroke-dasharray','4,3');
node.append('text').text(d=>d.label.substring(0,18)).attr('x',10).attr('y',3)
.attr('font-size','8px').attr('fill','rgba(194,200,222,.6)').attr('pointer-events','none');
// Tooltip on link hover
const relLabel=g.append('g').selectAll('.rel-lbl').data(links.filter(l=>l.rel)).join('text')
.attr('class','rel-lbl').attr('font-size','7px').attr('fill','rgba(140,150,180,.5)').attr('pointer-events','none');
sim.on('tick',()=>{
link.attr('x1',d=>d.source.x).attr('y1',d=>d.source.y).attr('x2',d=>d.target.x).attr('y2',d=>d.target.y);
node.attr('transform',d=>`translate(${d.x},${d.y})`);
relLabel.attr('x',d=>(d.source.x+d.target.x)/2).attr('y',d=>(d.source.y+d.target.y)/2).text(d=>(d.rel||'').substring(0,20));
});
$('net-ld').style.display='none';
function ds(ev,d){if(!ev.active)sim.alphaTarget(.3).restart();d.fx=d.x;d.fy=d.y}
function dd(ev,d){d.fx=ev.x;d.fy=ev.y}
function de(ev,d){if(!ev.active)sim.alphaTarget(0);d.fx=null;d.fy=null}
}
// ── FORENZIKA ───────────────────────────────────────────────────
async function loadForenzika(){
try{
const d=await fetch('https://api.rinet.one/api/v1/sport/forensics?limit=50').then(r=>r.json());
$('foren-wrap').innerHTML=d.map(f=>`
<div style="background:var(--bg2);border:1px solid var(--rim);border-radius:var(--r);padding:12px;margin-bottom:8px;display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:start;cursor:pointer" onclick="window.open('/sport/','_blank')">
<span class="bk ${f.severity==='CRITICAL'?'bk-r':f.severity==='HIGH'?'bk-a':'bk-b'}">${f.severity}</span>
<div>
<div style="font-size:12px;font-weight:600;color:var(--t0);margin-bottom:3px">${f.title||''}</div>
<div style="font-size:9px;color:var(--t4);font-family:var(--mono)">${f.finding_type||''}</div>
</div>
<span style="font-size:8px;color:var(--t4);font-family:var(--mono);white-space:nowrap">${f.created_at?.substring(0,10)||''}</span>
</div>`).join('')||'<div class="empty">Nema nalaza</div>';
}catch(e){$('foren-wrap').innerHTML='<div class="empty">Greška: '+e.message+'</div>'}
}
// ── KORISNICI ────────────────────────────────────────────────────
async function loadKorisnici(){
try{
const r=await fetch(`${API}/whoami`,{headers:{Authorization:`Bearer ${_auth?.token||''}`}}).then(r=>r.json());
// Fake user list since no /admin/users GET endpoint
$('usr-list').innerHTML=`
<div class="alert-bar warn"><div class="alert-ico"></div><div class="alert-txt">Upravljanje korisnicima — <strong>puna funkcionalnost Q3 2026</strong>. Koristite API <code>/api/admin/users</code> za kreiranje.</div></div>
<div class="cd"><div class="cd-h"><div class="cd-t">Postojeći korisnici</div></div><div class="cd-b">
<table class="dt"><thead><tr><th>Email</th><th>Uloga</th><th>Klub/Savez</th><th>Status</th></tr></thead>
<tbody>
<tr><td style="font-weight:600">damir@pgz.hr</td><td><span class="bk bk-r">pgz_admin</span></td><td></td><td><span class="bk bk-g">Aktivan</span></td></tr>
<tr><td>tajnik.zamet@rk.hr</td><td><span class="bk bk-b">klub_admin</span></td><td>RK Zamet</td><td><span class="bk bk-g">Aktivan</span></td></tr>
</tbody></table>
</div></div>`;
}catch(e){}
}
function openNovKorisnik(){alert('Funkcionalnost u razvoju — Q3 2026');}
function openNovClan(){alert('Funkcionalnost u razvoju — Q3 2026');}
function openNoviPregled(){alert('Integracija ZZJZ PGŽ — Q4 2026');}
// ── PANEL ────────────────────────────────────────────────────────
function openPanel(t=''){
$('panel').classList.add('open');
$('panel-overlay').style.display='block';
if(t){$('panel-hdr-t').textContent=t;$('panel-body').innerHTML=`<div class="ld"><div class="sp"></div>${t}</div>`}
}
function closePanel(){$('panel').classList.remove('open');$('panel-overlay').style.display='none'}
// ── TABS ─────────────────────────────────────────────────────────
function showTab(group,tabId){
document.querySelectorAll(`#${group} .tab`).forEach(t=>{t.classList.toggle('on',t.getAttribute('onclick')?.includes(`'${tabId}'`))});
document.querySelectorAll(`[id^="${group.replace('-tabs','-t-')}"]`).forEach(c=>c.classList.remove('on'));
$(tabId)?.classList.add('on');
}
// ── SORT ─────────────────────────────────────────────────────────
function sortDt(tblId,col){
const tbl=$(tblId); if(!tbl) return;
const tb=tbl.querySelector('tbody'); const rows=[...tb.querySelectorAll('tr')];
const th=tbl.querySelectorAll('th')[col];
const dir=th.classList.contains('asc')?-1:1;
tbl.querySelectorAll('th').forEach(h=>h.classList.remove('asc','desc'));
th.classList.add(dir===1?'asc':'desc');
rows.sort((a,b)=>{const av=(a.cells[col]?.innerText||'').trim(),bv=(b.cells[col]?.innerText||'').trim();const an=parseFloat(av.replace(/[^\d.-]/g,'')),bn=parseFloat(bv.replace(/[^\d.-]/g,''));return(!isNaN(an)&&!isNaN(bn))?dir*(an-bn):dir*av.localeCompare(bv,'hr')});
rows.forEach(r=>tb.appendChild(r));
}
// ── CSV EXPORT ────────────────────────────────────────────────────
function exportCSV(tblId){
const tbl=$(tblId); if(!tbl) return;
const csv=[...tbl.querySelectorAll('tr')].map(r=>[...r.cells].map(c=>'"'+c.innerText.replace(/"/g,'""')+'"').join(',')).join('\n');
const a=document.createElement('a');a.href='data:text/csv;charset=utf-8,\uFEFF'+encodeURIComponent(csv);a.download='pgz_export_'+Date.now()+'.csv';a.click();
}
// ── KEYBOARD ─────────────────────────────────────────────────────
document.addEventListener('keydown',e=>{
if(e.key==='Enter'&&$('login-screen').style.display!=='none') doLogin();
if(e.key==='Escape') closePanel();
if(e.key==='Enter'&&document.activeElement?.id?.startsWith('net-')) searchNetwork();
});
</script>
</body>
</html>