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:
+34
-2
@@ -136,7 +136,8 @@ table tr:hover td { background: rgba(26, 115, 232, 0.05); }
|
||||
.toast.err { border-left-color: var(--err); }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/sport/static/shared/sidebar.css">
|
||||
<script src="/sport/static/shared/sidebar.js" defer data-active="crm"></script>
|
||||
<script src="/sport/static/shared/sidebar.js" defer data-active="clanarine"></script>
|
||||
<style>body{padding-top:0}</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -158,6 +159,7 @@ table tr:hover td { background: rgba(26, 115, 232, 0.05); }
|
||||
<div class="tab" data-tab="obrasci" onclick="setTab('obrasci')">📝 Obrasci <span class="count" id="cnt-obrasci">…</span></div>
|
||||
<div class="tab" data-tab="stats" onclick="setTab('stats')">📊 Statistika</div>
|
||||
<div class="tab" data-tab="notifs" onclick="setTab('notifs')">🔔 Notifikacije <span class="count" id="cnt-notifs">…</span></div>
|
||||
<div class="tab" data-tab="emailtpl" onclick="setTab('emailtpl')">📨 E-mail templates</div>
|
||||
<div style="margin-left:auto;display:flex;align-items:center;gap:8px;padding:0 14px">
|
||||
<span style="font-size:11px;color:var(--t3)">ROLA:</span>
|
||||
<select id="g-role" onchange="setRole(this.value)" style="background:var(--bg3);border:1px solid var(--rim);color:var(--t1);padding:4px 8px;border-radius:4px;font-size:12px">
|
||||
@@ -178,6 +180,7 @@ table tr:hover td { background: rgba(26, 115, 232, 0.05); }
|
||||
<div id="page-obrasci" class="page" style="display:none"></div>
|
||||
<div id="page-stats" class="page" style="display:none"></div>
|
||||
<div id="page-notifs" class="page" style="display:none"></div>
|
||||
<div id="page-emailtpl" class="page" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<div id="modal-bg" class="modal-bg" onclick="if(event.target===this)closeModal()">
|
||||
@@ -258,6 +261,7 @@ function setTab(name) {
|
||||
if (name === 'obrasci') loadObrasci();
|
||||
if (name === 'stats') loadStats();
|
||||
if (name === 'notifs') loadNotifs();
|
||||
if (name === 'emailtpl') loadEmailTpl();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════
|
||||
@@ -294,7 +298,8 @@ async function loadClanarine() {
|
||||
<div class="grow"></div>
|
||||
<button class="btn" onclick="selectAllUnpaid()">☑ Sve nepladene</button>
|
||||
<button class="btn primary" onclick="bulkNotifySelected()">📧 Pošalji opomenu</button>
|
||||
<button class="btn" onclick="bulkUplatniceSelected()">📄 Generiraj uplatnice</button>
|
||||
<button class="btn" onclick="bulkUplatniceSelected()">📄 Generiraj uplatnice (lista)</button>
|
||||
<button class="btn" onclick="bulkUplatniceZipSelected()">🗜 Batch ZIP (PDF-ovi)</button>
|
||||
<button class="btn" onclick="newClanarinaModal()">+ Novo zaduženje</button>
|
||||
</div>
|
||||
<div id="cl-bulkbar" style="display:none;background:var(--bg3);border:1px solid var(--pgz-blue);border-radius:6px;padding:8px 14px;margin-bottom:10px;align-items:center;gap:14px">
|
||||
@@ -381,6 +386,33 @@ async function doBulkNotify(body) {
|
||||
} catch (e) { toast('Greška: ' + e.message, true); }
|
||||
}
|
||||
|
||||
async function bulkUplatniceZipSelected() {
|
||||
const sel = getSelectedClanarine();
|
||||
const body = sel.length ? {ids: sel.map(s => s.id), only_unpaid: false} : {};
|
||||
if (!sel.length && !confirm('Ništa nije odabrano — generirati ZIP za SVE dužnike?')) return;
|
||||
toast(`Generiranje ZIP-a (${sel.length || 'svi'})... može potrajati`);
|
||||
try {
|
||||
const r = await fetch(API + '/clanarine/bulk/uplatnice.zip', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!r.ok) {
|
||||
const t = await r.text();
|
||||
throw new Error(`HTTP ${r.status}: ${t.substring(0,200)}`);
|
||||
}
|
||||
const blob = await r.blob();
|
||||
const cnt = r.headers.get('X-Batch-Count') || '?';
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `hub3-batch-${new Date().toISOString().slice(0,10)}-${cnt}.zip`;
|
||||
document.body.appendChild(a); a.click(); a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
toast(`✓ ZIP preuzeto (${cnt} PDF-ova, ${(blob.size/1024).toFixed(0)} KB)`);
|
||||
} catch (e) { toast('Greška: ' + e.message, true); }
|
||||
}
|
||||
|
||||
async function bulkUplatniceSelected() {
|
||||
const sel = getSelectedClanarine();
|
||||
const body = sel.length ? {ids: sel.map(s => s.id)} : {};
|
||||
|
||||
Reference in New Issue
Block a user