PDF link target=_blank + nginx timeouts + priority filteri (samo s podacima)

nginx (sport.rinet.one):
- proxy_read_timeout 60s → 300s
- proxy_send_timeout 300s
- proxy_buffering off (PDF stream)
- client_max_body_size 50M → 100M

Endpoints:
- /api/v2/klubovi/financirani: +with_data filter (samo s potporama/godišnjakom/HNS)
- /api/v2/sportasi/filtered: +samo_priority +samo_s_hns

Frontend:
- PDF link target=_blank rel=noopener
- window._klub_only_priority = true (default)
- window._sportas_only_priority = true (default)

DB View:
- pgz_sport.v_nogomet_priority (prima_potpore, u_godisnjaku, ima_hns_roster)
This commit is contained in:
2026-05-05 13:51:07 +02:00
parent c6a5ec62aa
commit f7b5114f58
289 changed files with 37204 additions and 363 deletions
+64 -15
View File
@@ -69,19 +69,45 @@
const $ = (s, root) => (root||document).querySelector(s);
const esc = s => String(s==null?'':s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
// Tracks the currently-loaded user (null = guest). Set by setUserDisplay().
let _currentUser = null;
function readToken(){
try { return localStorage.getItem('jwt') || localStorage.getItem('access_token') || ''; }
catch(e){ return ''; }
}
function logout(){
if(!confirm('Odjava iz aplikacije?')) return;
try {
localStorage.removeItem('jwt');
localStorage.removeItem('access_token');
localStorage.removeItem('app-role');
return localStorage.getItem('pgz_access')
|| sessionStorage.getItem('pgz_access')
|| localStorage.getItem('jwt')
|| localStorage.getItem('access_token')
|| '';
} catch(e){ return ''; }
}
async function logout(){
if(!confirm('Odjava iz aplikacije?')) return;
// Revoke server-side (matches app.html logout flow, commit e07292b)
try {
const tok = readToken();
if(tok){
await fetch('/sport/api/auth/logout', {
method:'POST',
headers:{'Authorization':'Bearer '+tok}
}).catch(()=>{});
}
} catch(e){}
// Clear ALL session keys
['pgz_access','pgz_refresh','pgz_user','app-role','jwt','access_token','refresh_token','pgz_session_id'].forEach(k => {
try { localStorage.removeItem(k); sessionStorage.removeItem(k); } catch(e){}
});
location.href = '/sport/login';
}
function gotoLogin(){
// Preserve return URL so we land back here after sign-in
try {
const ret = encodeURIComponent(location.pathname + location.search + location.hash);
location.href = '/sport/login?next=' + ret;
} catch(e){
location.href = '/sport/login';
}
}
function initials(n){
if(!n) return '?';
const p = String(n).trim().split(/\s+/);
@@ -141,19 +167,23 @@
<div class="pgz-sb-mx" onclick="PGZSidebar.closeMobile()" title="Zatvori">✕</div>
</div>
<nav class="pgz-sb-nav" id="pgz-sb-nav">${renderSections(activeKey, user)}</nav>
<div class="pgz-sb-foot" id="pgz-sb-foot" onclick="PGZSidebar.toggleUserMenu(event)">
<div class="av" id="pgz-sb-av">PG</div>
<div class="pgz-sb-foot" id="pgz-sb-foot"
role="button" tabindex="0"
title="Klikni za prijavu / odjavu"
onclick="PGZSidebar.handleFootClick(event)"
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();PGZSidebar.handleFootClick(event);}">
<div class="av" id="pgz-sb-av">?</div>
<div class="ui">
<div class="un" id="pgz-sb-un">Gost</div>
<div class="ur" id="pgz-sb-ur">Demo</div>
<div class="ur" id="pgz-sb-ur">Klikni za prijavu</div>
</div>
<div class="caret">▾</div>
<div class="caret" id="pgz-sb-caret" title="Otvori izbornik" onclick="event.stopPropagation();PGZSidebar.toggleUserMenu(event)">▾</div>
<div class="pgz-user-menu" id="pgz-user-menu" onclick="event.stopPropagation()">
<a href="/app#profil"><span>👤</span><span>Moj profil</span></a>
<a href="/app#postavke"><span>⚙</span><span>Postavke</span></a>
<a href="/static/sport2.html"><span>🌐</span><span>Public portal</span></a>
<div class="sep"></div>
<a href="/login" id="pgz-menu-login"><span>🔑</span><span>Prijava</span></a>
<a href="/sport/login" id="pgz-menu-login"><span>🔑</span><span>Prijava</span></a>
<a class="danger" id="pgz-menu-logout" onclick="PGZSidebar.logout()" style="display:none"><span>⎋</span><span>Odjava</span></a>
</div>
</div>
@@ -171,13 +201,16 @@
}
function setUserDisplay(me){
_currentUser = me || null;
const un = $('#pgz-sb-un'), ur = $('#pgz-sb-ur'), av = $('#pgz-sb-av');
const foot = document.getElementById('pgz-sb-foot');
const loginLink = document.getElementById('pgz-menu-login');
const logoutLink = document.getElementById('pgz-menu-logout');
if(!me){
if(un) un.textContent = 'Gost';
if(ur) ur.textContent = 'Klikni za prijavu';
if(av){ av.textContent = '?'; av.innerHTML = av.innerHTML; }
if(foot) foot.setAttribute('title', 'Klikni za prijavu');
if(loginLink) loginLink.style.display = 'flex';
if(logoutLink) logoutLink.style.display = 'none';
return;
@@ -186,11 +219,12 @@
const role = me.user_type || '';
const avSrc = me.avatar_url || me.google_picture;
if(un) un.textContent = name;
if(ur) ur.textContent = role;
if(ur) ur.textContent = role || 'Korisnik';
if(av){
if(avSrc) av.innerHTML = `<img src="${esc(avSrc)}" alt="">`;
else av.textContent = initials(name);
}
if(foot) foot.setAttribute('title', 'Klikni za odjavu (' + (me.email || name) + ')');
if(loginLink) loginLink.style.display = 'none';
if(logoutLink) logoutLink.style.display = 'flex';
}
@@ -286,7 +320,22 @@
setTimeout(() => document.addEventListener('click', closer, true), 50);
}
},
logout
/* Single-click footer handler:
* - Guest (no user) → /sport/login
* - Logged in → logout() (revokes JWT, clears storage, redirects to login)
* The caret (▾) opens the full user menu (profil/postavke/portal/logout).
*/
handleFootClick(ev){
ev && ev.stopPropagation();
if(_currentUser){
return logout();
}
return gotoLogin();
},
gotoLogin,
logout,
// exposed for debugging / tests
_state: () => ({ user: _currentUser })
};
window.PGZSidebar = PGZSidebar;