1284 lines
92 KiB
HTML
1284 lines
92 KiB
HTML
<!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>
|
||
</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||'–'}</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||'–'}</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?'••'+c.oib.slice(-3):'–'}</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||'–'}</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>
|