/* 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 = `
Operativna platforma
${internalNavHTML ? `
Sekcije
` : ''}
PG
Gost
Demo
`; 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(); })();