DI exec: applied CC-DI Subagent A+B SQL — 3245 clanovi, Manuel Boras merged

This commit is contained in:
2026-05-05 09:04:14 +02:00
parent e7102c720d
commit 4e4d69c04a
6 changed files with 451 additions and 2 deletions
+136
View File
@@ -0,0 +1,136 @@
/* 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:
* <script src="/sport/static/oib_format.js"></script>
* 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);