/** * Gestão de utilizadores — painel direito do Controle de acesso * Criar · Editar · Congelar · Copiar · Eliminar */ (function () { 'use strict'; const ROLE_META = [ { value: 'super_admin', label: 'Super Admin', group: 'Ops', code: 'SU' }, { value: 'ops_lead', label: 'Chefe Ops', group: 'Ops', code: 'CO' }, { value: 'technician', label: 'Suporte', group: 'Ops', code: 'TEC' }, { value: 'noc', label: 'NOC', group: 'Ops', code: 'NOC' }, { value: 'sales_admin', label: 'Sales Admin', group: 'Comercial', code: 'SAD' }, { value: 'sales_support', label: 'Sales Support', group: 'Comercial', code: 'SSU' }, { value: 'finance', label: 'Financeiro', group: 'Negócio', code: 'FIN' }, { value: 'marketing', label: 'Marketing', group: 'Negócio', code: 'MKT' }, { value: 'seo', label: 'SEO', group: 'Negócio', code: 'SEO' }, { value: 'developer', label: 'Developer', group: 'Plataforma', code: 'DEV' }, { value: 'devops', label: 'DevOps', group: 'Plataforma', code: 'DVO' }, { value: 'security_analyst', label: 'Segurança / SOC', group: 'Plataforma', code: 'SOC' }, { value: 'content_editor', label: 'Conteúdo / CMS', group: 'Plataforma', code: 'CMS' }, { value: 'agentic_operator', label: 'Operador Agentes IA', group: 'Plataforma', code: 'AIO' }, { value: 'partner', label: 'Partner', group: 'Externo', code: 'PTR' }, ]; const ASSIGNABLE = ROLE_META.filter((r) => r.value !== 'super_admin'); const PORTAL_ID = 'um-modal-portal'; let users = []; let filterQ = ''; let filterStatus = 'all'; let modalMode = null; let modalUser = null; let createDefaultRole = 'technician'; let saving = false; let msg = ''; let currentHost = null; let currentOpts = null; function esc(s) { return String(s ?? '').replace(/&/g, '&').replace(//g, '>'); } function roleMeta(roleId) { return ROLE_META.find((r) => r.value === roleId) || { label: roleId, group: '—', code: '?' }; } function groupForRole(roleId) { return roleMeta(roleId).group; } function roleSelectGrouped(selected, includeSuper = false) { const list = includeSuper ? ROLE_META : ASSIGNABLE; const groups = [...new Set(list.map((r) => r.group))]; return groups.map((g) => { const opts = list.filter((r) => r.group === g).map((r) => `` ).join(''); return `${opts}`; }).join(''); } function filteredUsers(roleLock) { const q = filterQ.trim().toLowerCase(); return users.filter((u) => { if (roleLock && u.role !== roleLock && !(roleLock === 'super_admin' && u.username === 'root')) return false; if (filterStatus === 'active' && !u.active) return false; if (filterStatus === 'inactive' && u.active) return false; if (!q) return true; const hay = [u.username, u.email, u.display_name, roleMeta(u.role).label, roleMeta(u.role).group].join(' ').toLowerCase(); return hay.includes(q); }); } async function loadUsers() { const r = await fetchWithTimeout('/api/v1/auth/users', { headers: authHeaders() }); if (!r.ok) throw new Error(`${r.status}`); const data = await r.json(); users = data.users || []; return users; } async function apiUser(path, opts = {}) { const r = await fetchWithTimeout(`/api/v1/auth${path}`, { ...opts, headers: authHeaders({ 'Content-Type': 'application/json', ...(opts.headers || {}) }), }); if (!r.ok) { const t = await r.text(); throw new Error(t.slice(0, 200) || String(r.status)); } return r.json(); } function renderModalHtml() { if (!modalMode) return ''; const isCreate = modalMode === 'create'; const isClone = modalMode === 'clone'; const u = modalUser || {}; const title = isCreate ? 'Criar utilizador' : isClone ? 'Copiar utilizador' : 'Editar utilizador'; const defaultRole = isCreate ? createDefaultRole : (u.role || createDefaultRole); return ` `; } function syncModalPortal() { let portal = document.getElementById(PORTAL_ID); if (!modalMode) { if (portal) portal.innerHTML = ''; document.body.classList.remove('um-scroll-lock'); return; } if (!portal) { portal = document.createElement('div'); portal.id = PORTAL_ID; document.body.appendChild(portal); } portal.innerHTML = renderModalHtml(); document.body.classList.toggle('um-scroll-lock', true); bindModalEvents(portal); } function renderTable(list) { if (!list.length) { return '

Nenhum utilizador nesta função. Use «Criar utilizador» para adicionar.

'; } const rows = list.map((u) => { const rm = roleMeta(u.role); const isRoot = u.username === 'root'; return ` ${esc(u.display_name || u.username)} ${esc(u.email || u.username)} ${esc(rm.code)} ${u.active ? 'Activo' : 'Off'} ${!isRoot ? `` : ''} ${!isRoot ? `` : ''} `; }).join(''); return `
${rows}
UtilizadorPerfilEstadoAcções
`; } function renderContent(roleLock, roleMeta, embedded) { const list = filteredUsers(roleLock); const code = window.DeskAccessControlPanel?.ROLE_CODES?.[roleLock] || ''; const active = list.filter((u) => u.active).length; return `
Utilizadores ${embedded ? ` ${esc(code)} ${esc(roleMeta?.label || roleLock || '')} ${list.length} utilizador${list.length === 1 ? '' : 'es'} ` : ''}
${list.length} filtrados ${active} activos ${users.length} total
${currentOpts?.onRegistration ? '' : ''}
${renderTable(list)}
`; } function refreshList() { if (!currentHost) return; const roleLock = currentOpts?.roleFilter || null; const listEl = currentHost.querySelector('[data-um-list]'); if (listEl) listEl.innerHTML = renderTable(filteredUsers(roleLock)); bindRowEvents(currentHost); } let editCallback = null; async function handleSubmit(form) { const roleLock = currentOpts?.roleFilter || null; const fd = new FormData(form); saving = true; msg = ''; syncModalPortal(); try { if (modalMode === 'create') { await apiUser('/users', { method: 'POST', body: JSON.stringify({ email: fd.get('email'), password: fd.get('password'), role: fd.get('role'), display_name: fd.get('display_name') || null, active: !!form.querySelector('[name="active"]')?.checked, }), }); } else if (modalMode === 'clone') { const res = await apiUser(`/users/${encodeURIComponent(modalUser.username)}/clone`, { method: 'POST', body: JSON.stringify({ email: fd.get('email'), password: fd.get('password') || null, display_name: fd.get('display_name') || null, active: !!form.querySelector('[name="active"]')?.checked, }), }); if (res.generated_password) { window.alert(`Copiado.\nSenha gerada: ${res.generated_password}`); } } else { const payload = { display_name: fd.get('display_name') || null, role: fd.get('role'), active: !!form.querySelector('[name="active"]')?.checked, }; const pwd = fd.get('password'); if (pwd && String(pwd).length >= 6) payload.password = pwd; await apiUser(`/users/${encodeURIComponent(modalUser.username)}`, { method: 'PATCH', body: JSON.stringify(payload), }); } modalMode = null; modalUser = null; msg = ''; syncModalPortal(); await loadUsers(); refreshList(); if (editCallback) { const cb = editCallback; editCallback = null; cb(); } } catch (e) { msg = e.message; syncModalPortal(); } finally { saving = false; } } function closeModal() { modalMode = null; modalUser = null; msg = ''; saving = false; syncModalPortal(); } function bindModalEvents(portal) { portal.querySelector('.um-backdrop')?.addEventListener('click', (e) => { if (e.target !== e.currentTarget) return; closeModal(); }); portal.querySelectorAll('[data-um-close]').forEach((el) => { el.addEventListener('click', (e) => { e.stopPropagation(); closeModal(); }); }); portal.querySelector('[data-um-card]')?.addEventListener('mousedown', (e) => e.stopPropagation()); portal.querySelector('[data-um-form]')?.addEventListener('submit', async (e) => { e.preventDefault(); await handleSubmit(e.target); }); portal.querySelector('[data-um-role-select]')?.addEventListener('change', (e) => { const preview = portal.querySelector('[data-um-group-preview]'); if (preview) preview.textContent = groupForRole(e.target.value); }); } function bindRowEvents(host) { host.querySelectorAll('[data-um-edit]').forEach((btn) => { btn.addEventListener('click', (e) => { e.stopPropagation(); modalUser = users.find((u) => u.username === btn.dataset.umEdit); modalMode = 'edit'; msg = ''; syncModalPortal(); }); }); host.querySelectorAll('[data-um-clone]').forEach((btn) => { btn.addEventListener('click', (e) => { e.stopPropagation(); modalUser = users.find((u) => u.username === btn.dataset.umClone); modalMode = 'clone'; msg = ''; syncModalPortal(); }); }); host.querySelectorAll('[data-um-freeze]').forEach((btn) => { btn.addEventListener('click', async (e) => { e.stopPropagation(); const u = users.find((x) => x.username === btn.dataset.umFreeze); if (!u) return; const label = u.active ? 'Congelar' : 'Activar'; if (!window.confirm(`${label} ${u.username}?`)) return; try { await apiUser(`/users/${encodeURIComponent(u.username)}`, { method: 'PATCH', body: JSON.stringify({ active: !u.active }), }); await loadUsers(); refreshList(); } catch (err) { window.alert(err.message); } }); }); host.querySelectorAll('[data-um-delete]').forEach((btn) => { btn.addEventListener('click', async (e) => { e.stopPropagation(); const username = btn.dataset.umDelete; if (!window.confirm(`Eliminar ${username}? Irreversível.`)) return; try { await apiUser(`/users/${encodeURIComponent(username)}`, { method: 'DELETE' }); await loadUsers(); refreshList(); } catch (err) { window.alert(err.message); } }); }); } function bindEvents(host) { host.querySelector('[data-um-create]')?.addEventListener('click', () => { modalMode = 'create'; modalUser = null; msg = ''; syncModalPortal(); }); host.querySelector('[data-um-reg]')?.addEventListener('click', () => { if (typeof currentOpts?.onRegistration === 'function') currentOpts.onRegistration(); }); host.querySelector('[data-um-search]')?.addEventListener('input', (e) => { filterQ = e.target.value; clearTimeout(host._umTimer); host._umTimer = setTimeout(refreshList, 250); }); host.querySelector('[data-um-filter-status]')?.addEventListener('change', (e) => { filterStatus = e.target.value; refreshList(); }); bindRowEvents(host); } async function paint(host, opts = {}) { if (!host) return; currentHost = host; currentOpts = opts; createDefaultRole = opts.roleFilter || 'technician'; if (typeof canManageUsers === 'function' && !canManageUsers()) { host.innerHTML = '

Sem permissão.

'; return; } const firstLoad = !users.length; if (firstLoad) { host.innerHTML = '

Carregando utilizadores…

'; try { await loadUsers(); } catch (e) { host.innerHTML = `

Erro: ${esc(e.message)}

`; return; } } host.innerHTML = renderContent(opts.roleFilter, opts.roleMeta, opts.embedded); bindEvents(host); syncModalPortal(); } function reset() { users = []; filterQ = ''; filterStatus = 'all'; modalMode = null; modalUser = null; msg = ''; currentHost = null; currentOpts = null; closeModal(); } function openEdit(username, onSaved) { loadUsers().then(() => { modalUser = users.find((u) => u.username === username); if (!modalUser) return; modalMode = 'edit'; msg = ''; editCallback = onSaved || null; syncModalPortal(); }).catch((e) => window.alert(e.message)); } window.DeskUserManagement = { paint, reset, loadUsers, openEdit, ROLE_META }; })();