CC3: Unified sidebar with external portal links + collapsible icon mode

Shared module:
- /static/shared/sidebar.css   ← unified CSS (#pgz-sb, .pgz-collapsed, mobile overlay, tooltip)
- /static/shared/sidebar.js    ← auto-mounting JS shell + PGZSidebar API
   * Auto-renders #pgz-sb na <body> start (data-inline=1 to opt out)
   * NAV_EXTERNAL: Prijava, Aplikacija, Administracija, CRM, ERP, KPI, Audit, Public portal
   * Toggle (≡) -> localStorage 'sidebarCollapsed' (perzistira preko SVIH stranica)
   * Mobile <768px: ≡ burger + ✕ close, body backdrop
   * Loads /api/auth/me u footer (avatar/username/uloga); ⎋ logout briše JWT i ide na /login
   * data-active="<key>" highlight aktivnog portala

Page integration:
- sport2.html  ← inline NAV_EXTERNAL u buildNav() + "Portali" separator (zadrži postojeći sidebar)
- app.html     ← inline NAV_EXTERNAL u buildNav() (zadrži role-based interni nav, dopuni Portalima)
- admin.html   ← Portali stavke u <aside class="sidebar"> (matching .nav-item style)
- erp.html     ← Portali stavke u <aside class="sidebar"> (matching .nav-item style)
- crm.html     ← include shared sidebar.css + sidebar.js  data-active="crm"
- audit.html   ← include shared sidebar.css + sidebar.js  data-active="audit"
- kpi.html     ← include shared sidebar.css + sidebar.js  data-active="kpi"
- login.html   ← include shared sidebar.css + sidebar.js  data-active="login"

Backups: _backups/{*.cc3_pre_unified_sidebar.*}

Live verified: 8 pages serve HTTP 200; sidebar.css/js HTTP 200; portal markers per page OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Damir Radulić
2026-05-05 01:11:24 +02:00
parent bd3773434e
commit 8dce58c5f9
10 changed files with 431 additions and 1 deletions
+14
View File
@@ -60,6 +60,8 @@ button,input,select{font-family:inherit;font-size:inherit;outline:none}
.sb.collapsed .nav-i:hover::after{content:attr(data-label);position:absolute;left:58px;top:50%;transform:translateY(-50%);background:var(--bg3);color:var(--t0);padding:5px 10px;border-radius:4px;font-size:11.5px;white-space:nowrap;border:1px solid var(--rim);z-index:50;font-weight:600;pointer-events:none;box-shadow:2px 2px 8px rgba(0,0,0,.4)}
.sb.collapsed .sb-foot{font-size:0;padding:8px}
.sb.collapsed .sb-foot::before{content:"v2";font-size:9px;color:var(--t4)}
.sb.collapsed .nav-sep{font-size:0;padding:6px 0;text-align:center;border-top:1px dashed var(--rim);margin:6px 8px 4px}
.sb.collapsed .nav-ext span:last-child{display:none}
.main{margin-left:240px;flex:1;min-width:0;transition:margin-left .22s ease}
.sb.collapsed ~ .main{margin-left:58px}
@@ -570,9 +572,21 @@ function closePanel(){
document.addEventListener('keydown', e => { if(e.key==='Escape') closePanel(); });
//=========== NAVIGATION ===========
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'}
];
function buildNav(){
const nav = $('#nav');
nav.innerHTML = NAV_ITEMS.map(n => '<div class="nav-i '+(n.id===_state.section?'active':'')+'" data-id="'+n.id+'" data-label="'+n.label+'" onclick="navTo(\''+n.id+'\')"><span class="ic">'+n.ic+'</span><span class="lbl">'+n.label+'</span></div>').join('');
// PORTALI section + external links
nav.innerHTML += '<div class="nav-sep" style="padding:14px 14px 4px 14px;font-size:9.5px;color:var(--t4);text-transform:uppercase;letter-spacing:1.2px;font-weight:700;white-space:nowrap;overflow:hidden">Portali</div>';
nav.innerHTML += NAV_EXTERNAL.map(n => '<a class="nav-i nav-ext" href="'+n.href+'" data-id="'+n.id+'" data-label="'+esc(n.label)+'" style="color:var(--cyan);text-decoration:none"><span class="ic">'+n.ic+'</span><span class="lbl">'+esc(n.label)+'</span><span style="margin-left:auto;font-size:10px;opacity:.5">↗</span></a>').join('');
}
function toggleSidebar(){
const sb = document.getElementById('sb');