feat: /api/v2/analiza/* endpoints - sport analytics backend

This commit is contained in:
Damir Radulic
2026-05-16 00:28:12 +02:00
parent 7ca5d7d94e
commit aca5051418
1355 changed files with 321891 additions and 4128 deletions
+15 -9
View File
@@ -345,6 +345,7 @@ table tbody tr:hover{background:var(--bg3)}
<link rel="stylesheet" href="/static/shared/sidebar.css">
<script src="/static/shared/sidebar.js" defer data-active="profil"></script>
<script src="/static/oib_format.js" defer></script>
<script src="/static/shared/sortable.js" defer></script>
</head>
<body>
@@ -481,7 +482,7 @@ async function apiAuth(path, opts){
const onLogin = location.pathname.includes('/login');
if(!onLogin && !window.__pgz_redirecting){
window.__pgz_redirecting = true;
window.(window.__pgz_made_api_call ? location.href = '/login?reason=unauthorized' : console.warn('[auth] no token but no API call yet, skipping redirect'));
if(window.__pgz_made_api_call){location.href='/login?reason=unauthorized';}else{console.warn('[auth] no token but no API call yet, skipping redirect');}
}
return {__unauthorized:true, status:401};
}
@@ -511,7 +512,7 @@ const NAV_BY_ROLE = {
{id:'sportasi', ic:'\u{1F464}', label:'Sportaši'},
{id:'financije', ic:'€', label:'Financije'},
{id:'erp', ic:'\u{1F4BC}', label:'ERP', href:'/erp/full'},
{id:'crm', ic:'\u{1F4DD}', label:'CRM', href:'/crm/v2'},
{id:'crm', ic:'\u{1F4DD}', label:'CRM', href:'/crm'},
{id:'dokumenti', ic:'\u{1F4D6}', label:'Dokumenti'},
{id:'racuni', ic:'\u{1F9FE}', label:'Računi (OCR)', href:'/erp/full?tab=uploads'},
{id:'putni', ic:'\u{2708}', label:'Putni nalozi', href:'/erp/full?tab=putni'},
@@ -593,11 +594,13 @@ function applyMeToHeader(){
$('#user-name').innerHTML = esc(name) + `<span class="role-badge" id="user-role-badge">${esc(me.user_type||'')}</span>`;
$('#user-tenant').textContent = tenant;
$('#user-role-label')?.replaceChildren(document.createTextNode(roleLabel));
// Avatar topbar
// Avatar topbar — onError replaces <img> with initials so a 404 / dead URL doesn't show a broken-image icon
const _avInits = esc(initials(name));
const _avFallback = `this.onerror=null;this.style.display='none';if(this.parentElement){this.parentElement.textContent='${_avInits}';}`;
if(me.avatar_url){
$('#user-av').innerHTML = `<img src="${esc(me.avatar_url)}${me.avatar_url.includes('?')?'&':'?'}t=${Date.now()}" alt="">`;
$('#user-av').innerHTML = `<img src="${esc(me.avatar_url)}${me.avatar_url.includes('?')?'&':'?'}t=${Date.now()}" alt="" onerror="${_avFallback}">`;
} else if(me.google_picture){
$('#user-av').innerHTML = `<img src="${esc(me.google_picture)}" alt="">`;
$('#user-av').innerHTML = `<img src="${esc(me.google_picture)}" alt="" onerror="${_avFallback}">`;
} else {
$('#user-av').textContent = initials(name);
}
@@ -605,8 +608,8 @@ function applyMeToHeader(){
if($('#sf-name')) $('#sf-name').textContent = name;
if($('#sf-role')) $('#sf-role').textContent = roleLabel;
if($('#sf-av')){
if(me.avatar_url) $('#sf-av').innerHTML = `<img src="${esc(me.avatar_url)}${me.avatar_url.includes('?')?'&':'?'}t=${Date.now()}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%">`;
else if(me.google_picture) $('#sf-av').innerHTML = `<img src="${esc(me.google_picture)}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%">`;
if(me.avatar_url) $('#sf-av').innerHTML = `<img src="${esc(me.avatar_url)}${me.avatar_url.includes('?')?'&':'?'}t=${Date.now()}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%" onerror="${_avFallback}">`;
else if(me.google_picture) $('#sf-av').innerHTML = `<img src="${esc(me.google_picture)}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%" onerror="${_avFallback}">`;
else $('#sf-av').textContent = initials(name);
}
if($('#role-sub')) $('#role-sub').textContent = tenant || roleLabel;
@@ -895,8 +898,10 @@ function profileMe(){
function profileRender(){
const u = profileMe();
const name = u.full_name || ((u.ime||'')+' '+(u.prezime||'')).trim() || u.email || '—';
const av = u.avatar_url ? `<img src="${esc(u.avatar_url)}" alt="">`
: (u.google_picture ? `<img src="${esc(u.google_picture)}" alt="">` : esc(initials(name)));
const _avInits2 = esc(initials(name));
const _avFallback2 = `this.onerror=null;this.style.display='none';if(this.parentElement){this.parentElement.textContent='${_avInits2}';}`;
const av = u.avatar_url ? `<img src="${esc(u.avatar_url)}" alt="" onerror="${_avFallback2}">`
: (u.google_picture ? `<img src="${esc(u.google_picture)}" alt="" onerror="${_avFallback2}">` : esc(initials(name)));
const lastLogin = u.last_login ? new Date(u.last_login).toLocaleString('hr-HR') : '—';
const created = u.created_at ? new Date(u.created_at).toLocaleString('hr-HR') : '—';
const gdpr = u.gdpr_consent_at ? new Date(u.gdpr_consent_at).toLocaleDateString('hr-HR') : null;
@@ -2498,5 +2503,6 @@ window.renderPGZToggleBtn = function(){
};
</script>
<script src="/static/js/export_dropdown.js"></script>
<script src="/static/_ai_widget.js" defer></script>
</body>
</html>