CC3 R3: Sectioned sidebar redesign (DABI-style) — PORTAL/OPERATIVA/CRM/ERP/ANALITIKA/ADMIN

Reference: app.rinet.one/klasik/dabi — uppercase section headers + grouped items.

Shared module rewrite:
- /static/shared/sidebar.css   v2.0
   * 6 named sections, 240px expanded / 58px collapsed
   * Active item: gold left-border + transparent gradient fill
   * Hover: blue left-border accent
   * Section header hidden in collapsed mode (replaced with dashed separator)
   * Tooltip on hover (data-label) when collapsed
   * Mobile <768px overlay with backdrop
- /static/shared/sidebar.js    v2.0
   * SIDEBAR_SECTIONS = [PORTAL, OPERATIVA, CRM, ERP, ANALITIKA, ADMIN]
   * ADMIN section hidden unless user_type ∈ {pgz_admin, super_admin} (gated by /api/auth/me)
   * Cross-portal links (↗ marker) for items that target a different page
   * Same-page items trigger hashchange instead of full reload
   * Footer = avatar + name + role + ▾ user menu (Profil / Postavke / Public portal / Prijava ↔ Odjava)
   * localStorage 'sidebarCollapsed' persists across all 8 pages

Page integration:
- sport2.html  ← native .sb hidden; data-active=dashboard; hashchange→navTo
- app.html     ← native .sb hidden; data-active=profil; hashchange→navTo
- admin.html   ← native .sidebar hidden; data-active=korisnici
- erp.html     ← native .sidebar hidden; data-active=racuni
- crm.html     ← data-active=clanarine
- audit.html   ← data-active=audit (existing)
- kpi.html     ← data-active=kpi (existing)
- login.html   ← data-active=login (no item match → no highlight; user menu shows Prijava)

Backups: _backups/*.cc3_pre_redesign.{TS}

Live verified: all 8 pages HTTP 200; shared sidebar.css 200 (8664 B); sidebar.js 200 (12678 B); 6 sections present.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Damir Radulić
2026-05-05 01:42:16 +02:00
parent 7e674ad1ec
commit 3a79965899
7 changed files with 354 additions and 211 deletions
+12 -15
View File
@@ -32,7 +32,9 @@ button,input,select{font-family:inherit;font-size:inherit;outline:none}
::-webkit-scrollbar-thumb:hover{background:var(--pgz-blue2)}
.app{display:flex;min-height:100vh}
.sb{width:240px;background:linear-gradient(180deg,var(--bg1) 0%,var(--bg0) 100%);border-right:1px solid var(--rim);position:fixed;top:0;left:0;bottom:0;display:flex;flex-direction:column;z-index:10;transition:width .22s ease}
/* Native sidebar replaced by shared /static/shared/sidebar.* — kept hidden but DOM intact for legacy buildNav() calls */
.sb{display:none}
.sb-old{width:240px;background:linear-gradient(180deg,var(--bg1) 0%,var(--bg0) 100%);border-right:1px solid var(--rim);position:fixed;top:0;left:0;bottom:0;display:flex;flex-direction:column;z-index:10;transition:width .22s ease}
.sb-h{padding:18px 18px 14px;border-bottom:1px solid var(--rim);position:relative}
.sb-h .logo{font-weight:800;font-size:14px;color:var(--t0);letter-spacing:.5px;white-space:nowrap;overflow:hidden}
.sb-h .logo .g{color:var(--pgz-gold)}
@@ -63,8 +65,8 @@ button,input,select{font-family:inherit;font-size:inherit;outline:none}
.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}
.main{margin-left:0;flex:1;min-width:0;transition:margin-left .22s ease}
/* body.pgz-has-sb (from shared/sidebar.css) provides the left padding */
.tb{background:var(--bg1);border-bottom:1px solid var(--rim);padding:12px 22px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:5}
.tb-t{font-size:15px;font-weight:700;color:var(--t0)}
.tb-s{font-size:11px;color:var(--t2)}
@@ -222,6 +224,8 @@ a.tag:hover,.tag[onclick]:hover{transform:translateY(-1px);filter:brightness(1.1
.pp-stats{grid-template-columns:repeat(3,1fr)}
}
</style>
<link rel="stylesheet" href="/sport/static/shared/sidebar.css">
<script src="/sport/static/shared/sidebar.js" defer data-active="dashboard"></script>
</head>
<body>
@@ -572,22 +576,15 @@ 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('');
}
// Hashchange handler — accept routing from unified shared sidebar
window.addEventListener('hashchange', () => {
const h = (location.hash||'').replace(/^#/,'');
if(h && NAV_ITEMS.some(n => n.id===h)) navTo(h);
});
function toggleSidebar(){
const sb = document.getElementById('sb');
const tg = document.getElementById('sb-toggle');