/* PGŽ SPORT — Unified Sidebar v1.0
* dradulic@outlook.com / damir@rinet.one — 2026-05-05
*
* Usage on each page:
*
* // 0 (default) = render on load. 1 = call PGZSidebar.mount() yourself
*
* The script renders #pgz-sb at start of
, adds class "pgz-has-sb" to body
* (so existing layouts can be migrated). Pages that already have their own sidebar
* should pass data-skip="1" — only NAV_EXTERNAL portal links will be appended to
* an element with id="pgz-portal-mount" if present.
*/
(function(){
'use strict';
// ────────── Configuration ──────────
// Per-portal "internal" sections (left as a hint; pages typically own their own internal nav)
// External portal links — same on every page
const NAV_EXTERNAL = [
{id:'login', href:'/sport/login', ic:'\u{1F511}', label:'Prijava'},
{id:'app', href:'/sport/app', ic:'\u{1F4F1}', label:'Aplikacija'},
{id:'admin', href:'/sport/admin', ic:'\u{1F6E1}', label:'Administracija'},
{id:'crm', href:'/sport/crm', ic:'\u{1F465}', label:'CRM'},
{id:'erp', href:'/sport/erp', ic:'\u{1F4B0}', label:'ERP'},
{id:'kpi', href:'/sport/kpi', ic:'\u{1F4C8}', label:'KPI'},
{id:'audit', href:'/sport/audit', ic:'\u{1F4CB}', label:'Audit'},
{id:'sport2', href:'/sport/static/sport2.html', ic:'\u{1F310}', label:'Public portal'}
];
const STATE_KEY = 'sidebarCollapsed'; // shared across all pages
const $ = (s, root) => (root||document).querySelector(s);
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'); } catch(e){}
location.href = '/sport/login';
}
function initials(n){
if(!n) return '?';
const p = String(n).trim().split(/\s+/);
return ((p[0]||'')[0]||'').toUpperCase() + ((p[1]||'')[0]||'').toUpperCase();
}
function esc(s){
return String(s==null?'':s).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
}
// Try to read /api/auth/me for footer display (best effort)
async function tryLoadMe(){
const tok = readToken(); if(!tok) return null;
try {
const r = await fetch('/sport/api/auth/me', {headers:{'Authorization':'Bearer '+tok}});
if(!r.ok) return null;
return await r.json();
} catch(e){ return null; }
}
function renderShell(activeKey, internalNavHTML){
const sb = document.createElement('aside');
sb.id = 'pgz-sb';
sb.innerHTML = `
PGŽ SPORT
Operativna platforma
≡
✕
${internalNavHTML ? `Sekcije
` : ''}
`;
return sb;
}
function renderExternal(activeKey){
return NAV_EXTERNAL.map(n => `
${n.ic}
${esc(n.label)}
`).join('');
}
function renderBurger(){
if(document.getElementById('pgz-sb-burger')) return;
const b = document.createElement('div');
b.id = 'pgz-sb-burger';
b.className = 'pgz-sb-burger';
b.innerHTML = '≡';
b.onclick = () => PGZSidebar.openMobile();
document.body.appendChild(b);
}
function setUserDisplay(me){
if(!me){
$('#pgz-sb-un') && ($('#pgz-sb-un').textContent = 'Gost');
$('#pgz-sb-ur') && ($('#pgz-sb-ur').textContent = 'Demo · click Prijava');
$('#pgz-sb-av') && ($('#pgz-sb-av').textContent = '?');
return;
}
const name = me.full_name || ((me.ime||'')+' '+(me.prezime||'')).trim() || me.email || '—';
const role = me.user_type || '';
const av = me.avatar_url || me.google_picture;
if($('#pgz-sb-un')) $('#pgz-sb-un').textContent = name;
if($('#pgz-sb-ur')) $('#pgz-sb-ur').textContent = role;
const avEl = $('#pgz-sb-av');
if(avEl){
if(av) avEl.innerHTML = `
`;
else avEl.textContent = initials(name);
}
}
function applyCollapsedFromStorage(){
let col = false;
try { col = localStorage.getItem(STATE_KEY) === '1'; } catch(e){}
const sb = document.getElementById('pgz-sb');
if(!sb) return;
sb.classList.toggle('pgz-collapsed', col);
document.body.classList.toggle('pgz-sb-col', col);
}
// ────────── Public API ──────────
const PGZSidebar = {
NAV_EXTERNAL,
// Render: insert sidebar shell at document start; if a page provides internalNavHTML, use it
mount(opts){
opts = opts || {};
const activeKey = opts.activeKey || (document.currentScript && document.currentScript.dataset.active) || '';
const internalNavHTML = opts.internalNavHTML || '';
// Skip mount if the page already has its own sidebar AND a portal mount point is provided
if(opts.skipShell){
const mount = document.getElementById('pgz-portal-mount');
if(mount){ mount.innerHTML = renderExternal(activeKey); }
return;
}
const existing = document.getElementById('pgz-sb');
if(existing) existing.remove();
const sb = renderShell(activeKey, internalNavHTML);
document.body.insertBefore(sb, document.body.firstChild);
document.body.classList.add('pgz-has-sb');
renderBurger();
applyCollapsedFromStorage();
tryLoadMe().then(setUserDisplay);
},
// Append portal links to an existing custom sidebar (call this from a page's own buildNav)
appendPortalLinksTo(navEl, activeKey){
if(!navEl) return;
activeKey = activeKey || '';
navEl.insertAdjacentHTML('beforeend',
'Portali
'
);
navEl.insertAdjacentHTML('beforeend', renderExternal(activeKey));
},
toggle(){
const sb = document.getElementById('pgz-sb');
if(!sb) return;
const col = sb.classList.toggle('pgz-collapsed');
document.body.classList.toggle('pgz-sb-col', col);
try { localStorage.setItem(STATE_KEY, col ? '1' : '0'); } catch(e){}
},
openMobile(){
const sb = document.getElementById('pgz-sb');
if(!sb) return;
sb.classList.add('pgz-mobile-open');
document.body.classList.add('pgz-mobile-sb-open');
// close on backdrop click
const closer = (ev) => {
if(!sb.contains(ev.target) && ev.target.id !== 'pgz-sb-burger'){
PGZSidebar.closeMobile();
document.removeEventListener('click', closer, true);
}
};
setTimeout(() => document.addEventListener('click', closer, true), 50);
},
closeMobile(){
const sb = document.getElementById('pgz-sb');
if(!sb) return;
sb.classList.remove('pgz-mobile-open');
document.body.classList.remove('pgz-mobile-sb-open');
},
logout
};
window.PGZSidebar = PGZSidebar;
// Auto-mount unless data-inline=1
function autoMount(){
const cs = document.currentScript || Array.from(document.scripts).find(s => /sidebar\.js/.test(s.src||''));
const inline = cs && cs.dataset && cs.dataset.inline === '1';
if(inline) return; // page will call PGZSidebar.mount() itself
if(document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', () => PGZSidebar.mount({}));
} else {
PGZSidebar.mount({});
}
}
autoMount();
})();