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:
+18
-28
@@ -36,7 +36,8 @@ button,input,select,textarea{font-family:inherit;font-size:inherit;outline:none}
|
||||
|
||||
/* ============ LAYOUT ============ */
|
||||
.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 .sb hidden — shared sidebar (/static/shared/sidebar.*) handles sectioned menu */
|
||||
.sb{display:none}
|
||||
.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)}
|
||||
@@ -78,7 +79,7 @@ button,input,select,textarea{font-family:inherit;font-size:inherit;outline:none}
|
||||
.nav-ext:hover{color:var(--pgz-gold);background:var(--bg2)}
|
||||
.nav-ext.active{background:linear-gradient(90deg,var(--pgz-blue) 0%,var(--pgz-blue2) 100%);color:#fff}
|
||||
|
||||
.main{margin-left:240px;flex:1;min-width:0;transition:margin-left .22s ease}
|
||||
.main{margin-left:0;flex:1;min-width:0;transition:margin-left .22s ease}
|
||||
.sb.collapsed ~ .main{margin-left:58px}
|
||||
.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;gap:12px}
|
||||
.tb-t{font-size:15px;font-weight:700;color:var(--t0)}
|
||||
@@ -256,6 +257,8 @@ table tbody tr:hover{background:var(--bg3)}
|
||||
.role-switch{display:none}
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/sport/static/shared/sidebar.css">
|
||||
<script src="/sport/static/shared/sidebar.js" defer data-active="profil"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -600,35 +603,22 @@ function setRole(r){
|
||||
}
|
||||
|
||||
//=========== NAV ===========
|
||||
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'}
|
||||
];
|
||||
function buildNav(){
|
||||
const items = NAV_BY_ROLE[_state.role] || [];
|
||||
let html = items.map(n =>
|
||||
$('#nav').innerHTML = items.map(n =>
|
||||
`<div class="nav-i ${n.id===_state.section?'active':''}" data-id="${n.id}" data-label="${esc(n.label)}" onclick="navTo('${n.id}')">
|
||||
<span class="ic">${n.ic}</span>
|
||||
<span class="lbl">${esc(n.label)}</span>
|
||||
${n.badge?`<span class="badge">${n.badge}</span>`:''}
|
||||
</div>`
|
||||
).join('');
|
||||
// PORTALI separator + external links (active = 'app')
|
||||
html += '<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>';
|
||||
html += NAV_EXTERNAL.map(n =>
|
||||
`<a class="nav-i nav-ext ${n.id==='app'?'active':''}" href="${n.href}" data-id="${n.id}" data-label="${esc(n.label)}" style="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('');
|
||||
$('#nav').innerHTML = html;
|
||||
}
|
||||
window.addEventListener('hashchange', () => {
|
||||
const h = (location.hash||'').replace(/^#/,'');
|
||||
if(!h) return;
|
||||
const items = NAV_BY_ROLE[_state.role] || [];
|
||||
if(items.some(n => n.id===h)) navTo(h);
|
||||
});
|
||||
function navTo(id){
|
||||
_state.section = id;
|
||||
$$('.nav-i').forEach(el => el.classList.toggle('active', el.dataset.id===id));
|
||||
@@ -1158,7 +1148,7 @@ SECTIONS['pgz:racuni'] = () => `
|
||||
|
||||
SECTIONS['pgz:crm'] = () => `
|
||||
<div style="margin-bottom:12px">
|
||||
<a href="/sport/crm" target="_blank" class="btn primary" style="text-decoration:none">📋 Otvori CRM workspace (Članarine • Liječnički • Obrasci) — live API</a>
|
||||
<a href="/crm" target="_blank" class="btn primary" style="text-decoration:none">📋 Otvori CRM workspace (Članarine • Liječnički • Obrasci) — live API</a>
|
||||
</div>
|
||||
<div class="row-2">
|
||||
<div class="card">
|
||||
@@ -1255,7 +1245,7 @@ async function renderKalendar(opts){
|
||||
const isToday = (k === today.toISOString().slice(0,10));
|
||||
const evHtml = ev.slice(0,3).map(e => `<div style="font-size:10px;background:rgba(${e.color==='a'?'245,158,11':e.color==='b'?'26,115,232':'34,197,94'},0.18);padding:2px 4px;border-radius:3px;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(e.title)}${e.klub?' — '+esc(e.klub):''}">${esc(e.title.substring(0,28))}</div>`).join('');
|
||||
const more = ev.length > 3 ? `<div style="font-size:9px;color:var(--t3);margin-top:2px">+${ev.length-3} više</div>` : '';
|
||||
grid += `<div style="background:${isToday?'rgba(26,115,232,0.15)':'var(--bg2)'};border:1px solid ${isToday?'var(--pgz-blue)':'var(--rim)'};border-radius:6px;padding:6px;min-height:90px;${ev.length?'cursor:pointer':''}" ${ev.length?`onclick="alert('${esc(ev.map(x=>x.title+(x.klub?' — '+x.klub:'')).join('\\n').replace(/'/g,'\\\\\\'')\)}')"`:''}><div style="font-weight:600;font-size:13px;color:${isToday?'var(--pgz-blue)':'var(--t1)'}">${d}</div>${evHtml}${more}</div>`;
|
||||
grid += `<div style="background:${isToday?'rgba(26,115,232,0.15)':'var(--bg2)'};border:1px solid ${isToday?'var(--pgz-blue)':'var(--rim)'};border-radius:6px;padding:6px;min-height:90px;${ev.length?'cursor:pointer':''}" ><div style="font-weight:600;font-size:13px;color:${isToday?'var(--pgz-blue)':'var(--t1)'}">${d}</div>${evHtml}${more}</div>`;
|
||||
}
|
||||
grid += '</div>';
|
||||
|
||||
@@ -1505,7 +1495,7 @@ SECTIONS['klub:clanovi'] = () => `
|
||||
</div>`;
|
||||
|
||||
SECTIONS['klub:clanarine'] = () => `
|
||||
<div style="margin-bottom:10px"><a href="/sport/crm" target="_blank" class="btn primary" style="text-decoration:none">📋 Otvori live CRM (HUB-3 PDF + EPC QR generator)</a></div>
|
||||
<div style="margin-bottom:10px"><a href="/crm" target="_blank" class="btn primary" style="text-decoration:none">📋 Otvori live CRM (HUB-3 PDF + EPC QR generator)</a></div>
|
||||
<div class="row-2">
|
||||
<div class="card"><div class="card-h"><div class="card-t">€ Članarine 2026</div></div>
|
||||
<div class="kpi-grid"><div class="kpi g"><div class="kpi-l">Plaćeno</div><div class="kpi-v">80</div></div><div class="kpi r"><div class="kpi-l">Dug</div><div class="kpi-v">7</div></div></div>
|
||||
@@ -1527,7 +1517,7 @@ SECTIONS['klub:clanarine'] = () => `
|
||||
</div>`;
|
||||
|
||||
SECTIONS['klub:lijecnicki'] = () => `
|
||||
<div style="margin-bottom:10px"><a href="/sport/crm" target="_blank" class="btn primary" style="text-decoration:none">⚕ Otvori live CRM — pregledi + ZZJZ PGŽ scheduling</a></div>
|
||||
<div style="margin-bottom:10px"><a href="/crm" target="_blank" class="btn primary" style="text-decoration:none">⚕ Otvori live CRM — pregledi + ZZJZ PGŽ scheduling</a></div>
|
||||
<div class="card"><div class="card-h"><div class="card-t">⚕ Liječnički pregledi članova</div>
|
||||
<div class="card-actions"><button class="btn primary">📅 Bulk ZZJZ termini</button></div></div>
|
||||
<table><thead><tr><th>Član</th><th>Datum pregleda</th><th>Vrijedi do</th><th>Doktor</th><th>Status</th><th></th></tr></thead>
|
||||
@@ -1626,7 +1616,7 @@ SECTIONS['sportas:clanarina'] = () => `
|
||||
</div>`;
|
||||
|
||||
SECTIONS['sportas:lijecnicki'] = () => `
|
||||
<div style="margin-bottom:10px"><a href="/sport/crm" target="_blank" class="btn primary" style="text-decoration:none">⚕ Otvori live CRM — pregledi + ZZJZ PGŽ scheduling</a></div>`+`
|
||||
<div style="margin-bottom:10px"><a href="/crm" target="_blank" class="btn primary" style="text-decoration:none">⚕ Otvori live CRM — pregledi + ZZJZ PGŽ scheduling</a></div>`+`
|
||||
<div class="card"><div class="card-h"><div class="card-t">⚕ Moji liječnički pregledi</div></div>
|
||||
<div class="alert-card">
|
||||
<div class="at">⚠ Trenutni: vrijedi do 2026-08-15 (103 dana)</div>
|
||||
@@ -1658,7 +1648,7 @@ SECTIONS['sportas:dokumenti'] = () => `
|
||||
</div>`;
|
||||
|
||||
SECTIONS['sportas:obrasci'] = () => `
|
||||
<div style="margin-bottom:10px"><a href="/sport/crm" target="_blank" class="btn primary" style="text-decoration:none">📝 Otvori live obrasce — popuni i digitalno potpiši</a></div>
|
||||
<div style="margin-bottom:10px"><a href="/crm" target="_blank" class="btn primary" style="text-decoration:none">📝 Otvori live obrasce — popuni i digitalno potpiši</a></div>
|
||||
<div class="card"><div class="card-h"><div class="card-t">📝 Obrasci za potpis</div></div>
|
||||
<div class="alert-card crit">
|
||||
<div class="at">GDPR suglasnost 2026 — obvezno do 2026-06-01</div>
|
||||
|
||||
Reference in New Issue
Block a user