portal_publico/static/js/users-admin.js

456 lines
18 KiB
JavaScript

async function pidCreateUsuario(payload) {
return pidApiRequest("/usuarios/", { method: "POST", body: payload });
}
async function pidUpdateUsuario(id, payload) {
return pidApiRequest(`/usuarios/${id}/`, { method: "PATCH", body: payload });
}
async function pidDeleteUsuario(id) {
return pidApiRequest(`/usuarios/${id}/`, { method: "DELETE" });
}
document.addEventListener("DOMContentLoaded", async () => {
const listView = document.getElementById("ua-list-view");
const editView = document.getElementById("ua-edit-view");
if (!listView || !editView) return;
const me = await pidGetMe();
if (!me) return;
if (!me.gerencia_permissoes) {
window.location.href = "portal.html";
return;
}
let users = await pidFetchUsuarios();
let editingId = null;
const tableBody = document.getElementById("ua-table-body");
const footerCount = document.getElementById("ua-footer-count");
const searchInput = document.getElementById("ua-search-input");
const statusFiltros = document.getElementById("ua-status-filtros");
let statusFiltro = "ativos";
const newBtn = document.getElementById("ua-new-btn");
const backBtn = document.getElementById("ua-back-btn");
const cancelBtn = document.getElementById("ua-cancel-btn");
const saveBtn = document.getElementById("ua-save-btn");
const loginInput = document.getElementById("ua-login");
const nomeInput = document.getElementById("ua-nome");
const senhaInput = document.getElementById("ua-senha");
const senhaToggleBtn = document.getElementById("ua-senha-toggle");
const profileChecklist = document.getElementById("ua-profile-checklist");
const formError = document.getElementById("ua-form-error");
const departamentoChecklist = document.getElementById("ua-departamento-checklist");
const departamentoNewInput = document.getElementById("ua-departamento-new");
const departamentoAddBtn = document.getElementById("ua-departamento-add-btn");
const departamentoError = document.getElementById("ua-departamento-error");
const emailInput = document.getElementById("ua-email");
const codigoFolhaInput = document.getElementById("ua-codigo-folha");
const codigoQuestorInput = document.getElementById("ua-codigo-questor");
const codigoTareffaInput = document.getElementById("ua-codigo-tareffa");
const codigoContabitInput = document.getElementById("ua-codigo-contabit");
const ramalInput = document.getElementById("ua-ramal");
const dataAniversarioInput = document.getElementById("ua-data-aniversario");
const ativoInput = document.getElementById("ua-ativo");
const liderancaInput = document.getElementById("ua-lideranca");
const lideradosField = document.getElementById("ua-liderados-field");
const lideradosSearchInput = document.getElementById("ua-liderados-search");
const lideradosSelectAllInput = document.getElementById("ua-liderados-select-all");
const lideradosChecklist = document.getElementById("ua-liderados-checklist");
liderancaInput.addEventListener("change", () => {
lideradosField.hidden = !liderancaInput.checked;
});
pidBindPasswordToggle(senhaToggleBtn, senhaInput);
function profileNomes(perfis) {
return perfis.length ? perfis.map((p) => p.nome).join(", ") : "Sem perfil vinculado";
}
function renderList() {
const term = searchInput.value.trim().toLowerCase();
const rows = users
.filter((u) => statusFiltro === "todos" || (statusFiltro === "ativos") === u.is_active)
.filter((u) => !term || u.nome.toLowerCase().includes(term) || u.username.toLowerCase().includes(term))
.slice()
.sort((a, b) => a.nome.localeCompare(b.nome));
tableBody.innerHTML = rows.length
? rows
.map(
(u) => `
<tr>
<td>${u.username}</td>
<td><a href="#" class="pa-name-link" data-ua-edit="${u.id}">${u.nome}</a></td>
<td>${profileNomes(u.perfis)}</td>
<td><span class="status-pill ${u.is_active ? "status-pill--ativo" : "status-pill--inativo"}">${u.is_active ? "Ativo" : "Inativo"}</span></td>
<td>
<div class="pa-row-actions">
<button type="button" class="icon-btn" data-ua-edit="${u.id}" aria-label="Editar usuário">
<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-ua-toggle-active="${u.id}" aria-label="${u.is_active ? "Desativar usuário" : "Ativar usuário"}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v8" stroke-linecap="round"/><path d="M6.2 6.2a8 8 0 1 0 11.6 0" stroke-linecap="round"/></svg>
</button>
<button type="button" class="icon-btn" data-ua-delete="${u.id}" aria-label="Excluir usuário">
<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="5">Nenhum usuário encontrado.</td></tr>`;
footerCount.textContent = `${rows.length} ${rows.length === 1 ? "item" : "itens"}`;
}
async function fillProfileChecklist(selectedCodigos) {
const perfis = (await pidFetchPerfis()).slice().sort((a, b) => a.nome.localeCompare(b.nome));
profileChecklist.innerHTML = perfis
.map(
(p) => `
<label class="checklist-item">
<input type="checkbox" value="${p.codigo}" ${selectedCodigos.includes(p.codigo) ? "checked" : ""} />
<span>${p.nome}</span>
</label>`
)
.join("");
}
let lideradosCandidatos = [];
let lideradosSelecionados = new Set();
function departamentosTexto(usuario) {
return (usuario.departamentos || []).map((d) => d.nome).join(", ");
}
function lideradosFiltrados() {
const termo = lideradosSearchInput.value.trim().toLowerCase();
return lideradosCandidatos.filter(
(u) => !termo || u.nome.toLowerCase().includes(termo) || departamentosTexto(u).toLowerCase().includes(termo)
);
}
function renderLideradosChecklist() {
const filtrados = lideradosFiltrados();
lideradosChecklist.innerHTML = filtrados.length
? filtrados
.map(
(u) => `
<label class="checklist-item">
<input type="checkbox" value="${u.id}" ${lideradosSelecionados.has(u.id) ? "checked" : ""} />
<span class="checklist-item__info">
<span class="checklist-item__nome">${u.nome}</span>
<span class="checklist-item__departamento">${departamentosTexto(u) || "—"}</span>
</span>
</label>`
)
.join("")
: `<p class="checklist-empty">Nenhum usuário encontrado.</p>`;
const todosMarcados = filtrados.length > 0 && filtrados.every((u) => lideradosSelecionados.has(u.id));
const algumMarcado = filtrados.some((u) => lideradosSelecionados.has(u.id));
lideradosSelectAllInput.disabled = filtrados.length === 0;
lideradosSelectAllInput.checked = todosMarcados;
lideradosSelectAllInput.indeterminate = !todosMarcados && algumMarcado;
}
function fillLideradosChecklist(selectedIds, excludeId) {
lideradosCandidatos = users
.filter((u) => u.id !== excludeId)
.slice()
.sort((a, b) => a.nome.localeCompare(b.nome));
lideradosSelecionados = new Set(selectedIds);
lideradosSearchInput.value = "";
renderLideradosChecklist();
}
function selectedLideradosIds() {
return Array.from(lideradosSelecionados);
}
lideradosChecklist.addEventListener("change", (event) => {
const checkbox = event.target.closest('input[type="checkbox"]');
if (!checkbox) return;
const id = Number(checkbox.value);
if (checkbox.checked) lideradosSelecionados.add(id);
else lideradosSelecionados.delete(id);
renderLideradosChecklist();
});
lideradosSearchInput.addEventListener("input", renderLideradosChecklist);
lideradosSelectAllInput.addEventListener("change", () => {
const filtrados = lideradosFiltrados();
if (lideradosSelectAllInput.checked) {
filtrados.forEach((u) => lideradosSelecionados.add(u.id));
} else {
filtrados.forEach((u) => lideradosSelecionados.delete(u.id));
}
renderLideradosChecklist();
});
let departamentosCache = [];
async function fillDepartamentoChecklist(selectedIds) {
departamentosCache = (await pidFetchDepartamentos()).slice().sort((a, b) => a.nome.localeCompare(b.nome));
departamentoChecklist.innerHTML = departamentosCache
.map(
(d) => `
<div class="ua-departamento-item">
<label class="checklist-item">
<input type="checkbox" value="${d.id}" ${selectedIds.includes(d.id) ? "checked" : ""} />
<span>${d.nome}</span>
</label>
<div class="ua-departamento-item__actions">
<button type="button" class="ua-departamento-action" data-dep-edit="${d.id}" aria-label="Renomear departamento">
<svg width="14" height="14" 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="ua-departamento-action" data-dep-delete="${d.id}" aria-label="Excluir departamento">
<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>
</div>`
)
.join("");
}
function selectedDepartamentoIds() {
return Array.from(departamentoChecklist.querySelectorAll('input[type="checkbox"]:checked')).map((cb) =>
Number(cb.value)
);
}
async function openEdit(id) {
formError.textContent = "";
departamentoError.textContent = "";
departamentoNewInput.value = "";
senhaInput.value = "";
senhaInput.type = "password";
if (senhaToggleBtn) senhaToggleBtn.setAttribute("aria-label", "Mostrar senha");
if (id === null) {
editingId = null;
loginInput.value = "";
nomeInput.value = "";
senhaInput.placeholder = "Digite a senha";
emailInput.value = "";
codigoFolhaInput.value = "";
codigoQuestorInput.value = "";
codigoTareffaInput.value = "";
codigoContabitInput.value = "";
ramalInput.value = "";
dataAniversarioInput.value = "";
ativoInput.checked = true;
ativoInput.disabled = false;
liderancaInput.checked = false;
lideradosField.hidden = true;
await fillProfileChecklist([]);
await fillDepartamentoChecklist([]);
fillLideradosChecklist([], null);
} else {
const account = users.find((u) => u.id === id);
if (!account) return;
editingId = id;
loginInput.value = account.username;
nomeInput.value = account.nome;
senhaInput.placeholder = "Deixe em branco para manter a senha atual";
emailInput.value = account.email || "";
codigoFolhaInput.value = account.codigo_folha || "";
codigoQuestorInput.value = account.codigo_questor || "";
codigoTareffaInput.value = account.codigo_tareffa || "";
codigoContabitInput.value = account.codigo_contabit || "";
ramalInput.value = account.ramal || "";
dataAniversarioInput.value = account.data_aniversario || "";
ativoInput.checked = account.is_active;
ativoInput.disabled = account.id === me.id;
liderancaInput.checked = account.lideranca;
lideradosField.hidden = !account.lideranca;
await fillProfileChecklist(account.perfis.map((p) => p.codigo));
await fillDepartamentoChecklist(account.departamentos.map((d) => d.id));
fillLideradosChecklist(account.liderados.map((u) => u.id), account.id);
}
listView.hidden = true;
editView.hidden = false;
}
function closeEdit() {
editView.hidden = true;
listView.hidden = false;
renderList();
}
searchInput.addEventListener("input", renderList);
statusFiltros.addEventListener("click", (event) => {
const chip = event.target.closest("[data-ua-status]");
if (!chip) return;
statusFiltro = chip.dataset.uaStatus;
statusFiltros.querySelectorAll(".ua-status-chip").forEach((el) => el.classList.toggle("is-active", el === chip));
renderList();
});
newBtn.addEventListener("click", () => openEdit(null));
backBtn.addEventListener("click", closeEdit);
cancelBtn.addEventListener("click", closeEdit);
departamentoAddBtn.addEventListener("click", async () => {
const nome = departamentoNewInput.value.trim();
if (!nome) {
departamentoError.textContent = "Informe o nome do departamento.";
return;
}
departamentoError.textContent = "";
try {
const novo = await pidCreateDepartamento({ nome });
const selected = selectedDepartamentoIds();
selected.push(novo.id);
await fillDepartamentoChecklist(selected);
departamentoNewInput.value = "";
} catch (e) {
departamentoError.textContent = e.message;
}
});
departamentoChecklist.addEventListener("click", async (event) => {
const editBtn = event.target.closest("[data-dep-edit]");
if (editBtn) {
const id = Number(editBtn.getAttribute("data-dep-edit"));
const dep = departamentosCache.find((d) => d.id === id);
if (!dep) return;
const novoNome = window.prompt("Novo nome do departamento:", dep.nome);
if (novoNome === null) return;
const nome = novoNome.trim();
if (!nome) return;
departamentoError.textContent = "";
try {
await pidUpdateDepartamento(id, { nome });
await fillDepartamentoChecklist(selectedDepartamentoIds());
} catch (e) {
departamentoError.textContent = e.message;
}
return;
}
const deleteBtn = event.target.closest("[data-dep-delete]");
if (deleteBtn) {
const id = Number(deleteBtn.getAttribute("data-dep-delete"));
const dep = departamentosCache.find((d) => d.id === id);
if (!dep) return;
if (!window.confirm(`Excluir o departamento "${dep.nome}"?`)) return;
try {
await pidDeleteDepartamento(id);
const selected = selectedDepartamentoIds().filter((depId) => depId !== id);
await fillDepartamentoChecklist(selected);
} catch (e) {
window.alert(e.message);
}
}
});
tableBody.addEventListener("click", async (event) => {
const editTarget = event.target.closest("[data-ua-edit]");
if (editTarget) {
event.preventDefault();
await openEdit(Number(editTarget.getAttribute("data-ua-edit")));
return;
}
const deleteTarget = event.target.closest("[data-ua-delete]");
if (deleteTarget) {
const id = Number(deleteTarget.getAttribute("data-ua-delete"));
if (id === me.id) {
window.alert("Você não pode excluir o usuário com o qual está logado.");
return;
}
const account = users.find((u) => u.id === id);
if (account && window.confirm(`Excluir o usuário "${account.nome}"?`)) {
await pidDeleteUsuario(id);
users = await pidFetchUsuarios();
renderList();
}
return;
}
const toggleTarget = event.target.closest("[data-ua-toggle-active]");
if (toggleTarget) {
const id = Number(toggleTarget.getAttribute("data-ua-toggle-active"));
if (id === me.id) {
window.alert("Você não pode desativar o usuário com o qual está logado.");
return;
}
const account = users.find((u) => u.id === id);
if (!account) return;
const acao = account.is_active ? "Desativar" : "Ativar";
if (window.confirm(`${acao} o usuário "${account.nome}"?`)) {
await pidUpdateUsuario(id, { is_active: !account.is_active });
users = await pidFetchUsuarios();
renderList();
}
}
});
saveBtn.addEventListener("click", async () => {
const username = loginInput.value.trim();
const nome = nomeInput.value.trim();
const senha = senhaInput.value;
const perfis = Array.from(profileChecklist.querySelectorAll('input[type="checkbox"]:checked')).map((cb) =>
Number(cb.value)
);
const departamentos = selectedDepartamentoIds();
const email = emailInput.value.trim();
const codigo_folha = codigoFolhaInput.value.trim();
const codigo_questor = codigoQuestorInput.value.trim();
const codigo_tareffa = codigoTareffaInput.value.trim();
const codigo_contabit = codigoContabitInput.value.trim();
const ramal = ramalInput.value.trim();
const data_aniversario = dataAniversarioInput.value || null;
const lideranca = liderancaInput.checked;
const liderados = selectedLideradosIds();
if (!username || !nome) {
formError.textContent = "Informe login e nome do usuário.";
return;
}
if (editingId === null && !senha) {
formError.textContent = "Informe uma senha para o novo usuário.";
return;
}
formError.textContent = "";
const payload = {
username,
nome,
senha,
perfis,
departamentos,
email,
codigo_folha,
codigo_questor,
codigo_tareffa,
codigo_contabit,
ramal,
data_aniversario,
lideranca,
liderados,
is_active: ativoInput.checked,
};
try {
if (editingId === null) {
await pidCreateUsuario(payload);
} else {
await pidUpdateUsuario(editingId, payload);
}
users = await pidFetchUsuarios();
closeEdit();
} catch (e) {
formError.textContent = e.message;
}
});
renderList();
});