645 lines
23 KiB
JavaScript
645 lines
23 KiB
JavaScript
let PID_MODULES = [];
|
|
let PID_MODULE_APPS = {};
|
|
|
|
async function pidFetchCatalogo() {
|
|
return pidApiRequest("/catalogo/");
|
|
}
|
|
|
|
async function pidFetchPerfis() {
|
|
return pidApiRequest("/perfis/");
|
|
}
|
|
|
|
async function pidCreatePerfil(payload) {
|
|
return pidApiRequest("/perfis/", { method: "POST", body: payload });
|
|
}
|
|
|
|
async function pidUpdatePerfil(codigo, payload) {
|
|
return pidApiRequest(`/perfis/${codigo}/`, { method: "PUT", body: payload });
|
|
}
|
|
|
|
async function pidUpdatePerfilPermissoes(codigo, permissoes) {
|
|
return pidApiRequest(`/perfis/${codigo}/`, { method: "PATCH", body: { permissoes } });
|
|
}
|
|
|
|
async function pidDeletePerfil(codigo) {
|
|
return pidApiRequest(`/perfis/${codigo}/`, { method: "DELETE" });
|
|
}
|
|
|
|
/** Usada também por users-admin.js (lista de usuários + perfis vinculados). */
|
|
async function pidFetchUsuarios() {
|
|
return pidApiRequest("/usuarios/");
|
|
}
|
|
|
|
/** Usadas também por users-admin.js (checklist de departamentos + cadastro inline de um novo). */
|
|
async function pidFetchDepartamentos() {
|
|
return pidApiRequest("/departamentos/");
|
|
}
|
|
|
|
async function pidCreateDepartamento(payload) {
|
|
return pidApiRequest("/departamentos/", { method: "POST", body: payload });
|
|
}
|
|
|
|
async function pidUpdateDepartamento(id, payload) {
|
|
return pidApiRequest(`/departamentos/${id}/`, { method: "PATCH", body: payload });
|
|
}
|
|
|
|
async function pidDeleteDepartamento(id) {
|
|
return pidApiRequest(`/departamentos/${id}/`, { method: "DELETE" });
|
|
}
|
|
|
|
async function pidSetUsuarioPerfis(id, perfisCodigos) {
|
|
return pidApiRequest(`/usuarios/${id}/`, { method: "PATCH", body: { perfis: perfisCodigos } });
|
|
}
|
|
|
|
function pidPerfilFromApi(p) {
|
|
return {
|
|
codigo: p.codigo,
|
|
nome: p.nome,
|
|
ativo: p.ativo,
|
|
gerenciaPermissoes: p.gerencia_permissoes,
|
|
permissions: p.permissoes,
|
|
};
|
|
}
|
|
|
|
function pidPerfilToApi(p) {
|
|
return {
|
|
nome: p.nome,
|
|
ativo: p.ativo,
|
|
gerencia_permissoes: p.gerenciaPermissoes,
|
|
permissoes: p.permissions,
|
|
};
|
|
}
|
|
|
|
function pidAppsFor(moduleKey, value) {
|
|
const apps = {};
|
|
(PID_MODULE_APPS[moduleKey] || []).forEach((entry) => {
|
|
if (entry.tools) {
|
|
entry.tools.forEach((tool) => {
|
|
apps[tool.key] = value;
|
|
});
|
|
} else {
|
|
apps[entry.key] = value;
|
|
}
|
|
});
|
|
return apps;
|
|
}
|
|
|
|
function pidEmptyPermissions(allEnabled) {
|
|
const perms = {};
|
|
PID_MODULES.forEach((m) => {
|
|
perms[m.key] = { enabled: !!allEnabled, apps: pidAppsFor(m.key, !!allEnabled) };
|
|
});
|
|
return perms;
|
|
}
|
|
|
|
/** Achata PID_MODULES x PID_MODULE_APPS numa lista plana de "aplicações"
|
|
* pesquisáveis (uma por entrada de MODULE_APPS — app simples ou subgrupo),
|
|
* usada pelo popup "Nova Aplicação". Cada item mantém `tools` (ou `null`
|
|
* pra app simples) pra o painel de gerenciamento saber quantas colunas de
|
|
* acesso desenhar por perfil. */
|
|
function pidFlattenAplicacoes() {
|
|
const result = [];
|
|
PID_MODULES.forEach((modulo) => {
|
|
(PID_MODULE_APPS[modulo.key] || []).forEach((entry) => {
|
|
result.push({
|
|
moduleKey: modulo.key,
|
|
moduleLabel: modulo.label,
|
|
key: entry.key,
|
|
label: entry.label,
|
|
tools: entry.tools || null,
|
|
});
|
|
});
|
|
});
|
|
return result;
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
const listView = document.getElementById("pa-list-view");
|
|
const editView = document.getElementById("pa-edit-view");
|
|
if (!listView || !editView) return;
|
|
|
|
const me = await pidGetMe();
|
|
if (!me) return;
|
|
if (!me.gerencia_permissoes) {
|
|
window.location.href = "portal.html";
|
|
return;
|
|
}
|
|
|
|
const catalogo = await pidFetchCatalogo();
|
|
PID_MODULES = catalogo.modulos;
|
|
PID_MODULE_APPS = catalogo.aplicacoes_por_modulo;
|
|
|
|
let profiles = (await pidFetchPerfis()).map(pidPerfilFromApi);
|
|
let draftProfile = null;
|
|
let activeModuleKey = PID_MODULES[0].key;
|
|
let activeTab = "permissoes";
|
|
const expandedModules = new Set();
|
|
const expandedSubgroups = new Set();
|
|
let appSearchSelected = null;
|
|
|
|
const tableBody = document.getElementById("pa-table-body");
|
|
const footerCount = document.getElementById("pa-footer-count");
|
|
const listSearchInput = document.getElementById("pa-search-input");
|
|
const newBtn = document.getElementById("pa-new-btn");
|
|
const backBtn = document.getElementById("pa-back-btn");
|
|
const cancelBtn = document.getElementById("pa-cancel-btn");
|
|
const saveBtn = document.getElementById("pa-save-btn");
|
|
const formCodigo = document.getElementById("pa-codigo");
|
|
const formNome = document.getElementById("pa-nome");
|
|
const formError = document.getElementById("pa-nome-error");
|
|
const moduleListEl = document.getElementById("pa-module-list");
|
|
const treePanel = document.getElementById("pa-tree-panel");
|
|
const treeSearchInput = document.getElementById("pa-tree-search-input");
|
|
const usersAddSelect = document.getElementById("pa-user-add-select");
|
|
const usersAddBtn = document.getElementById("pa-user-add-btn");
|
|
const usersListEl = document.getElementById("pa-user-list");
|
|
const appSearchBtn = document.getElementById("pa-app-search-btn");
|
|
const appSearchModal = document.getElementById("pa-app-search-modal");
|
|
const appSearchCloseBtn = document.getElementById("pa-app-search-close-btn");
|
|
const appSearchField = document.getElementById("pa-app-search-field");
|
|
const appSearchInput = document.getElementById("pa-app-search-input");
|
|
const appSearchResults = document.getElementById("pa-app-search-results");
|
|
const appManagePanel = document.getElementById("pa-app-manage-panel");
|
|
const appManageBackBtn = document.getElementById("pa-app-manage-back-btn");
|
|
const appManageTitle = document.getElementById("pa-app-manage-title");
|
|
const appManageSearchInput = document.getElementById("pa-app-manage-search");
|
|
const appManageThead = document.getElementById("pa-app-manage-thead");
|
|
const appManageTbody = document.getElementById("pa-app-manage-tbody");
|
|
|
|
function renderList() {
|
|
const term = listSearchInput.value.trim().toLowerCase();
|
|
const rows = profiles
|
|
.filter((p) => !term || p.nome.toLowerCase().includes(term))
|
|
.slice()
|
|
.sort((a, b) => a.codigo - b.codigo);
|
|
|
|
tableBody.innerHTML = rows.length
|
|
? rows
|
|
.map(
|
|
(p) => `
|
|
<tr>
|
|
<td>${p.codigo}</td>
|
|
<td>
|
|
<a href="#" class="pa-name-link" data-pa-edit="${p.codigo}">${p.nome}</a>
|
|
${p.gerenciaPermissoes ? '<span class="badge-total-access">Acesso total</span>' : ""}
|
|
</td>
|
|
<td>
|
|
<div class="pa-row-actions">
|
|
<button type="button" class="icon-btn" data-pa-edit="${p.codigo}" aria-label="Editar perfil">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9" stroke-linecap="round"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z" stroke-linejoin="round"/></svg>
|
|
</button>
|
|
<button type="button" class="icon-btn" data-pa-delete="${p.codigo}" aria-label="Excluir perfil">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2m-8 0 1 12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1l1-12" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>`
|
|
)
|
|
.join("")
|
|
: `<tr class="pa-empty-row"><td colspan="3">Nenhum perfil encontrado.</td></tr>`;
|
|
|
|
footerCount.textContent = `${rows.length} ${rows.length === 1 ? "item" : "itens"}`;
|
|
}
|
|
|
|
function renderModuleList() {
|
|
moduleListEl.innerHTML = PID_MODULES.map((m, idx) => {
|
|
const enabled = draftProfile.permissions[m.key].enabled;
|
|
return `
|
|
<button type="button" class="pa-module-item ${m.key === activeModuleKey ? "is-active" : ""}" data-pa-module="${m.key}">
|
|
<span class="pa-module-item__index">${idx + 1}</span>
|
|
<span class="pa-module-item__label">${m.label}</span>
|
|
<svg class="pa-module-item__check" ${enabled ? "" : "hidden"} width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
</button>`;
|
|
}).join("");
|
|
}
|
|
|
|
function renderLeaf(perm, tool, term) {
|
|
const match = !term || tool.label.toLowerCase().includes(term);
|
|
return `
|
|
<label class="perm-node perm-node--leaf ${match ? "" : "is-hidden"}">
|
|
<input type="checkbox" data-perm-app="${tool.key}" ${perm.apps[tool.key] ? "checked" : ""} />
|
|
<span>${tool.label}</span>
|
|
</label>`;
|
|
}
|
|
|
|
function renderEntry(perm, entry, term) {
|
|
if (!entry.tools) {
|
|
return renderLeaf(perm, entry, term);
|
|
}
|
|
|
|
const subgroupKey = `${activeModuleKey}__${entry.key}`;
|
|
const subgroupExpanded = expandedSubgroups.has(subgroupKey);
|
|
const groupChecked = entry.tools.every((tool) => perm.apps[tool.key]);
|
|
const anyMatch =
|
|
!term || entry.label.toLowerCase().includes(term) || entry.tools.some((tool) => tool.label.toLowerCase().includes(term));
|
|
|
|
return `
|
|
<div class="perm-node perm-node--group ${anyMatch ? "" : "is-hidden"}">
|
|
<button type="button" class="perm-expand ${subgroupExpanded ? "is-open" : ""}" data-perm-subgroup-expand="${subgroupKey}" aria-label="Expandir">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
</button>
|
|
<label>
|
|
<input type="checkbox" data-perm-group="${entry.key}" ${groupChecked ? "checked" : ""} />
|
|
<span>${entry.label}</span>
|
|
</label>
|
|
</div>
|
|
<div class="perm-children perm-children--nested" data-perm-children ${subgroupExpanded ? "" : "hidden"}>
|
|
${entry.tools.map((tool) => renderLeaf(perm, tool, term)).join("")}
|
|
</div>`;
|
|
}
|
|
|
|
function renderTree() {
|
|
const perm = draftProfile.permissions[activeModuleKey];
|
|
const moduleLabel = PID_MODULES.find((m) => m.key === activeModuleKey).label;
|
|
const term = treeSearchInput.value.trim().toLowerCase();
|
|
const expanded = expandedModules.has(activeModuleKey);
|
|
const entries = PID_MODULE_APPS[activeModuleKey] || [];
|
|
const hasEntries = entries.length > 0;
|
|
|
|
treePanel.innerHTML = `
|
|
<label class="perm-node perm-node--root">
|
|
<input type="checkbox" data-perm-role="root" ${perm.enabled ? "checked" : ""} />
|
|
<span>Todos</span>
|
|
</label>
|
|
<div class="perm-node perm-node--module">
|
|
${
|
|
hasEntries
|
|
? `<button type="button" class="perm-expand ${expanded ? "is-open" : ""}" data-perm-expand aria-label="Expandir">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
</button>`
|
|
: ""
|
|
}
|
|
<label>
|
|
<input type="checkbox" data-perm-role="module" ${perm.enabled ? "checked" : ""} />
|
|
<span>${moduleLabel}</span>
|
|
</label>
|
|
</div>
|
|
${
|
|
hasEntries
|
|
? `<div class="perm-children" data-perm-children ${expanded ? "" : "hidden"}>
|
|
${entries.map((entry) => renderEntry(perm, entry, term)).join("")}
|
|
</div>`
|
|
: ""
|
|
}
|
|
`;
|
|
}
|
|
|
|
async function renderUsers() {
|
|
const usuarios = await pidFetchUsuarios();
|
|
const isLinked = (u) => u.perfis.some((p) => p.codigo === draftProfile.codigo);
|
|
const linked = usuarios.filter(isLinked);
|
|
const unlinked = usuarios.filter((u) => !isLinked(u));
|
|
|
|
usersListEl.innerHTML = linked.length
|
|
? linked
|
|
.map(
|
|
(u) => `
|
|
<div class="pa-user-row">
|
|
<span>${u.nome} <span class="pa-user-login">(${u.username})</span></span>
|
|
<button type="button" class="pa-user-remove" data-pa-user-unlink="${u.id}" aria-label="Remover vínculo">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/></svg>
|
|
</button>
|
|
</div>`
|
|
)
|
|
.join("")
|
|
: `<p class="pa-users-empty">Nenhum usuário vinculado a este perfil.</p>`;
|
|
|
|
usersAddSelect.innerHTML = unlinked.length
|
|
? `<option value="">Selecione um usuário...</option>` +
|
|
unlinked
|
|
.map((u) => {
|
|
const otherNames = u.perfis.map((p) => p.nome);
|
|
const suffix = otherNames.length ? ` — também em: ${otherNames.join(", ")}` : "";
|
|
return `<option value="${u.id}">${u.nome} (${u.username})${suffix}</option>`;
|
|
})
|
|
.join("")
|
|
: `<option value="">Nenhum usuário disponível</option>`;
|
|
}
|
|
|
|
function renderTabs() {
|
|
document.querySelectorAll("[data-pa-tab]").forEach((tab) => {
|
|
tab.classList.toggle("is-active", tab.getAttribute("data-pa-tab") === activeTab);
|
|
});
|
|
document.querySelectorAll("[data-pa-tab-panel]").forEach((panel) => {
|
|
panel.hidden = panel.getAttribute("data-pa-tab-panel") !== activeTab;
|
|
});
|
|
}
|
|
|
|
function renderAppSearchResults() {
|
|
const term = appSearchInput.value.trim().toLowerCase();
|
|
const items = pidFlattenAplicacoes().filter((a) => {
|
|
if (!term) return true;
|
|
if (a.label.toLowerCase().includes(term)) return true;
|
|
if (a.moduleLabel.toLowerCase().includes(term)) return true;
|
|
return !!(a.tools && a.tools.some((tool) => tool.label.toLowerCase().includes(term)));
|
|
});
|
|
|
|
appSearchResults.innerHTML = items.length
|
|
? items
|
|
.map(
|
|
(a) => `
|
|
<button type="button" class="pa-app-result-item" data-pa-app-module="${a.moduleKey}" data-pa-app-key="${a.key}">
|
|
<span class="pa-app-result-item__label">${a.label}</span>
|
|
<span class="pa-app-result-item__module">${a.moduleLabel}</span>
|
|
</button>`
|
|
)
|
|
.join("")
|
|
: `<p class="checklist-empty">Nenhuma aplicação encontrada.</p>`;
|
|
}
|
|
|
|
function renderAppManageTable() {
|
|
const entry = appSearchSelected;
|
|
if (!entry) return;
|
|
const term = appManageSearchInput.value.trim().toLowerCase();
|
|
const tools = entry.tools || [{ key: entry.key, label: "Acesso" }];
|
|
|
|
appManageThead.innerHTML = `
|
|
<tr>
|
|
<th>Perfil</th>
|
|
${tools.map((tool) => `<th>${tool.label.split(" (")[0]}</th>`).join("")}
|
|
</tr>`;
|
|
|
|
const rows = profiles
|
|
.filter((p) => !term || p.nome.toLowerCase().includes(term))
|
|
.slice()
|
|
.sort((a, b) => a.nome.localeCompare(b.nome));
|
|
|
|
appManageTbody.innerHTML = rows.length
|
|
? rows
|
|
.map((p) => {
|
|
const perm = p.permissions[entry.moduleKey];
|
|
return `
|
|
<tr>
|
|
<td>${p.nome} ${p.ativo ? "" : '<span class="status-pill status-pill--inativo">Inativo</span>'}</td>
|
|
${tools
|
|
.map(
|
|
(tool) => `
|
|
<td><input type="checkbox" data-pa-app-perfil="${p.codigo}" data-pa-app-key="${tool.key}" ${
|
|
perm.apps[tool.key] ? "checked" : ""
|
|
} /></td>`
|
|
)
|
|
.join("")}
|
|
</tr>`;
|
|
})
|
|
.join("")
|
|
: `<tr class="pa-empty-row"><td colspan="${1 + tools.length}">Nenhum perfil encontrado.</td></tr>`;
|
|
}
|
|
|
|
function openAppManage(entry) {
|
|
appSearchSelected = entry;
|
|
appManageTitle.textContent = `${entry.label} — ${entry.moduleLabel}`;
|
|
appManageSearchInput.value = "";
|
|
appSearchField.hidden = true;
|
|
appManagePanel.hidden = false;
|
|
renderAppManageTable();
|
|
}
|
|
|
|
function closeAppManage() {
|
|
appSearchSelected = null;
|
|
appManagePanel.hidden = true;
|
|
appSearchField.hidden = false;
|
|
}
|
|
|
|
function openAppSearchModal() {
|
|
closeAppManage();
|
|
appSearchInput.value = "";
|
|
renderAppSearchResults();
|
|
appSearchModal.hidden = false;
|
|
}
|
|
|
|
function closeAppSearchModal() {
|
|
appSearchModal.hidden = true;
|
|
}
|
|
|
|
async function openEdit(codigo) {
|
|
if (codigo === null) {
|
|
draftProfile = {
|
|
codigo: null,
|
|
nome: "",
|
|
ativo: true,
|
|
gerenciaPermissoes: false,
|
|
permissions: pidEmptyPermissions(false),
|
|
};
|
|
} else {
|
|
const found = profiles.find((p) => p.codigo === codigo);
|
|
if (!found) return;
|
|
draftProfile = JSON.parse(JSON.stringify(found));
|
|
}
|
|
|
|
activeModuleKey = PID_MODULES[0].key;
|
|
activeTab = "permissoes";
|
|
expandedModules.clear();
|
|
expandedSubgroups.clear();
|
|
formError.textContent = "";
|
|
|
|
formCodigo.value = draftProfile.codigo === null ? "Gerado ao salvar" : draftProfile.codigo;
|
|
formNome.value = draftProfile.nome;
|
|
|
|
renderTabs();
|
|
renderModuleList();
|
|
renderTree();
|
|
|
|
listView.hidden = true;
|
|
editView.hidden = false;
|
|
|
|
await renderUsers();
|
|
}
|
|
|
|
function closeEdit() {
|
|
editView.hidden = true;
|
|
listView.hidden = false;
|
|
renderList();
|
|
}
|
|
|
|
listSearchInput.addEventListener("input", renderList);
|
|
|
|
newBtn.addEventListener("click", () => openEdit(null));
|
|
backBtn.addEventListener("click", closeEdit);
|
|
cancelBtn.addEventListener("click", closeEdit);
|
|
|
|
appSearchBtn.addEventListener("click", openAppSearchModal);
|
|
appSearchCloseBtn.addEventListener("click", closeAppSearchModal);
|
|
appSearchModal.addEventListener("click", (event) => {
|
|
if (event.target === appSearchModal) closeAppSearchModal();
|
|
});
|
|
appSearchInput.addEventListener("input", renderAppSearchResults);
|
|
appManageBackBtn.addEventListener("click", closeAppManage);
|
|
appManageSearchInput.addEventListener("input", renderAppManageTable);
|
|
|
|
appSearchResults.addEventListener("click", (event) => {
|
|
const btn = event.target.closest("[data-pa-app-key]");
|
|
if (!btn) return;
|
|
const moduleKey = btn.getAttribute("data-pa-app-module");
|
|
const key = btn.getAttribute("data-pa-app-key");
|
|
const entry = pidFlattenAplicacoes().find((a) => a.moduleKey === moduleKey && a.key === key);
|
|
if (entry) openAppManage(entry);
|
|
});
|
|
|
|
appManageTbody.addEventListener("change", async (event) => {
|
|
const checkbox = event.target.closest("[data-pa-app-key]");
|
|
if (!checkbox || !appSearchSelected) return;
|
|
const codigo = Number(checkbox.getAttribute("data-pa-app-perfil"));
|
|
const key = checkbox.getAttribute("data-pa-app-key");
|
|
const profile = profiles.find((p) => p.codigo === codigo);
|
|
if (!profile) return;
|
|
|
|
const perm = profile.permissions[appSearchSelected.moduleKey];
|
|
const checked = checkbox.checked;
|
|
const wasEnabled = perm.enabled;
|
|
perm.apps[key] = checked;
|
|
if (checked && !perm.enabled) perm.enabled = true;
|
|
|
|
checkbox.disabled = true;
|
|
try {
|
|
await pidUpdatePerfilPermissoes(profile.codigo, profile.permissions);
|
|
} catch (e) {
|
|
perm.apps[key] = !checked;
|
|
perm.enabled = wasEnabled;
|
|
checkbox.checked = !checked;
|
|
window.alert(`Não foi possível salvar: ${e.message}`);
|
|
} finally {
|
|
checkbox.disabled = false;
|
|
}
|
|
});
|
|
|
|
tableBody.addEventListener("click", async (event) => {
|
|
const editTarget = event.target.closest("[data-pa-edit]");
|
|
if (editTarget) {
|
|
event.preventDefault();
|
|
await openEdit(Number(editTarget.getAttribute("data-pa-edit")));
|
|
return;
|
|
}
|
|
const deleteTarget = event.target.closest("[data-pa-delete]");
|
|
if (deleteTarget) {
|
|
const codigo = Number(deleteTarget.getAttribute("data-pa-delete"));
|
|
const profile = profiles.find((p) => p.codigo === codigo);
|
|
if (profile && window.confirm(`Excluir o perfil "${profile.nome}"?`)) {
|
|
await pidDeletePerfil(codigo);
|
|
profiles = (await pidFetchPerfis()).map(pidPerfilFromApi);
|
|
renderList();
|
|
}
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll("[data-pa-tab]").forEach((tab) => {
|
|
tab.addEventListener("click", () => {
|
|
activeTab = tab.getAttribute("data-pa-tab");
|
|
renderTabs();
|
|
});
|
|
});
|
|
|
|
moduleListEl.addEventListener("click", (event) => {
|
|
const btn = event.target.closest("[data-pa-module]");
|
|
if (!btn) return;
|
|
activeModuleKey = btn.getAttribute("data-pa-module");
|
|
renderModuleList();
|
|
renderTree();
|
|
});
|
|
|
|
treeSearchInput.addEventListener("input", renderTree);
|
|
|
|
treePanel.addEventListener("click", (event) => {
|
|
const subgroupExpandBtn = event.target.closest("[data-perm-subgroup-expand]");
|
|
if (subgroupExpandBtn) {
|
|
const key = subgroupExpandBtn.getAttribute("data-perm-subgroup-expand");
|
|
if (expandedSubgroups.has(key)) {
|
|
expandedSubgroups.delete(key);
|
|
} else {
|
|
expandedSubgroups.add(key);
|
|
}
|
|
renderTree();
|
|
return;
|
|
}
|
|
const expandBtn = event.target.closest("[data-perm-expand]");
|
|
if (!expandBtn) return;
|
|
if (expandedModules.has(activeModuleKey)) {
|
|
expandedModules.delete(activeModuleKey);
|
|
} else {
|
|
expandedModules.add(activeModuleKey);
|
|
}
|
|
renderTree();
|
|
});
|
|
|
|
treePanel.addEventListener("change", (event) => {
|
|
const perm = draftProfile.permissions[activeModuleKey];
|
|
const roleInput = event.target.closest("[data-perm-role]");
|
|
if (roleInput) {
|
|
const checked = roleInput.checked;
|
|
perm.enabled = checked;
|
|
Object.keys(perm.apps).forEach((key) => {
|
|
perm.apps[key] = checked;
|
|
});
|
|
renderModuleList();
|
|
renderTree();
|
|
return;
|
|
}
|
|
const groupInput = event.target.closest("[data-perm-group]");
|
|
if (groupInput) {
|
|
const entryKey = groupInput.getAttribute("data-perm-group");
|
|
const entries = PID_MODULE_APPS[activeModuleKey] || [];
|
|
const entry = entries.find((e) => e.key === entryKey);
|
|
if (entry && entry.tools) {
|
|
entry.tools.forEach((tool) => {
|
|
perm.apps[tool.key] = groupInput.checked;
|
|
});
|
|
}
|
|
renderTree();
|
|
return;
|
|
}
|
|
const appInput = event.target.closest("[data-perm-app]");
|
|
if (appInput) {
|
|
perm.apps[appInput.getAttribute("data-perm-app")] = appInput.checked;
|
|
}
|
|
});
|
|
|
|
async function linkUser() {
|
|
const id = Number(usersAddSelect.value);
|
|
if (!id) return;
|
|
const usuarios = await pidFetchUsuarios();
|
|
const account = usuarios.find((u) => u.id === id);
|
|
if (!account) return;
|
|
const codes = account.perfis.map((p) => p.codigo);
|
|
if (!codes.includes(draftProfile.codigo)) codes.push(draftProfile.codigo);
|
|
await pidSetUsuarioPerfis(id, codes);
|
|
await renderUsers();
|
|
}
|
|
|
|
usersAddBtn.addEventListener("click", linkUser);
|
|
|
|
usersListEl.addEventListener("click", async (event) => {
|
|
const removeBtn = event.target.closest("[data-pa-user-unlink]");
|
|
if (!removeBtn) return;
|
|
const id = Number(removeBtn.getAttribute("data-pa-user-unlink"));
|
|
const usuarios = await pidFetchUsuarios();
|
|
const account = usuarios.find((u) => u.id === id);
|
|
if (!account) return;
|
|
const codes = account.perfis.map((p) => p.codigo).filter((c) => c !== draftProfile.codigo);
|
|
await pidSetUsuarioPerfis(id, codes);
|
|
await renderUsers();
|
|
});
|
|
|
|
saveBtn.addEventListener("click", async () => {
|
|
const nome = formNome.value.trim();
|
|
if (!nome) {
|
|
formError.textContent = "Informe o nome do perfil.";
|
|
return;
|
|
}
|
|
formError.textContent = "";
|
|
draftProfile.nome = nome;
|
|
|
|
try {
|
|
if (draftProfile.codigo === null) {
|
|
await pidCreatePerfil(pidPerfilToApi(draftProfile));
|
|
} else {
|
|
await pidUpdatePerfil(draftProfile.codigo, pidPerfilToApi(draftProfile));
|
|
}
|
|
profiles = (await pidFetchPerfis()).map(pidPerfilFromApi);
|
|
closeEdit();
|
|
} catch (e) {
|
|
formError.textContent = e.message;
|
|
}
|
|
});
|
|
|
|
renderList();
|
|
});
|