/* oib_format.js — unified role-based OIB display * Author: Damir Radulić (damir@rinet.one / dradulic@outlook.com) * Date: 2026-05-05 * Description: Single source of truth for OIB rendering across all PGŽ Sport * static pages. Role hierarchy (per pgz_sport.users.user_type): * super_admin -> full OIB everywhere * pgz_admin -> full OIB across PGŽ tenant * savez_admin -> full OIB for own savez_id (context-aware) * klub_admin -> full OIB for own klub_id (context-aware) * others -> masked: first 3 + 6 dots + last 2 (e.g. 067••••••03) * Usage: * * formatOib('12345678901') // role auto-detected from pgz_user * formatOib(o.oib, {savez_id: 1, klub_id: 7}) // pass scope for context-aware * maskOib('12345678901') // force masked */ (function (g) { 'use strict'; var TOKEN_KEYS = ['pgz_access', 'jwt', 'access_token']; var USER_KEYS = ['pgz_user']; // PGŽ-tier: always sees full PII for everything var FULL_VISIBILITY_ROLES = { 'super_admin': 1, 'pgz_admin': 1, 'pgz_user': 1, 'pgz_finance': 1, 'pgz_zzjz': 1, 'admin': 1 // legacy bearer token role }; // Scope-restricted roles var SAVEZ_ROLES = { 'savez_admin': 1, 'savez_user': 1 }; var KLUB_ROLES = { 'klub_admin': 1, 'klub_user': 1, 'klub_trener': 1, 'klub_clan': 1 }; function _readUser() { for (var i = 0; i < USER_KEYS.length; i++) { var k = USER_KEYS[i]; try { var raw = localStorage.getItem(k) || sessionStorage.getItem(k); if (raw) return JSON.parse(raw); } catch (e) { /* ignore */ } } return null; } function _readToken() { for (var i = 0; i < TOKEN_KEYS.length; i++) { var k = TOKEN_KEYS[i]; var t = localStorage.getItem(k) || sessionStorage.getItem(k); if (t) return t; } return null; } function _decodeJwt(tok) { if (!tok || tok.split('.').length !== 3) return null; try { var p = tok.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'); while (p.length % 4) p += '='; return JSON.parse(atob(p)); } catch (e) { return null; } } /** Returns {role, klub_id, savez_id, email} from JWT or stored user. */ function getUserCtx() { var u = _readUser() || {}; var jwt = _decodeJwt(_readToken()) || {}; var role = u.user_type || u.role || jwt.role || jwt.user_type || 'viewer'; var klub_id = u.klub_id != null ? u.klub_id : (jwt.tenant_scope && jwt.tenant_scope.klub_id); var savez_id = u.savez_id != null ? u.savez_id : (jwt.tenant_scope && jwt.tenant_scope.savez_id); return { role: String(role || 'viewer').toLowerCase(), klub_id: klub_id == null ? null : Number(klub_id), savez_id: savez_id == null ? null : Number(savez_id), email: u.email || jwt.email || null }; } /** Force-masked rendering: first 3 + 6 dots + last 2. e.g. 067••••••03 */ function maskOib(oib) { if (oib == null) return '—'; var s = String(oib); if (s.length < 6) return '•'.repeat(s.length); return s.slice(0, 3) + '••••••' + s.slice(-2); } /** * Decide whether to show full OIB based on caller role and (optional) row scope. * @param {string|number} oib raw OIB value (or already-masked from backend) * @param {object} [scope] optional context: {klub_id, savez_id} * if provided, savez_admin / klub_admin can see * full OIB for rows in their own scope only * @returns {string} formatted OIB */ function formatOib(oib, scope) { if (oib == null || oib === '') return '—'; var s = String(oib); // Backend already masked — pass through (we cannot un-mask client-side) if (s.indexOf('•') !== -1 || s.indexOf('*') !== -1) return s; var ctx = getUserCtx(); var r = ctx.role; if (FULL_VISIBILITY_ROLES[r]) return s; if (scope && typeof scope === 'object') { if (SAVEZ_ROLES[r] && ctx.savez_id != null && scope.savez_id != null && Number(scope.savez_id) === ctx.savez_id) return s; if (KLUB_ROLES[r] && ctx.klub_id != null && scope.klub_id != null && Number(scope.klub_id) === ctx.klub_id) return s; } else { // No scope passed — savez/klub admins default to full only when scope not given // (they would only ever query their own scope via tenanted endpoints) if (SAVEZ_ROLES[r] || KLUB_ROLES[r]) return s; } return maskOib(s); } /** Returns true if the current user can see full PII (any OIB). */ function canSeeFullOib(scope) { var ctx = getUserCtx(); if (FULL_VISIBILITY_ROLES[ctx.role]) return true; if (!scope) return SAVEZ_ROLES[ctx.role] || KLUB_ROLES[ctx.role] || false; if (SAVEZ_ROLES[ctx.role] && scope.savez_id != null && Number(scope.savez_id) === ctx.savez_id) return true; if (KLUB_ROLES[ctx.role] && scope.klub_id != null && Number(scope.klub_id) === ctx.klub_id) return true; return false; } g.formatOib = formatOib; g.maskOib = maskOib; g.getUserCtx = g.getUserCtx || getUserCtx; g.canSeeFullOib = canSeeFullOib; })(window);