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 sortHeaders = Array.from(document.querySelectorAll("#ua-table th[data-sort]")); let sortKey = "nome"; let sortDir = 1; 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"); function departamentosTexto(usuario) { return (usuario.departamentos || []).map((d) => d.nome).join(", "); } const seletorLiderados = pidCriarSeletorDuplo({ available: { selectAll: document.getElementById("ua-liderados-available-select-all"), sortHeader: document.getElementById("ua-liderados-available-sort-nome"), searchPrincipal: document.getElementById("ua-liderados-available-search-nome"), searchSecundaria: document.getElementById("ua-liderados-available-search-departamento"), body: document.getElementById("ua-liderados-available-body"), footer: document.getElementById("ua-liderados-available-footer"), acaoBtn: document.getElementById("ua-liderados-available-link-btn"), }, linked: { selectAll: document.getElementById("ua-liderados-linked-select-all"), sortHeader: document.getElementById("ua-liderados-linked-sort-nome"), searchPrincipal: document.getElementById("ua-liderados-linked-search-nome"), searchSecundaria: document.getElementById("ua-liderados-linked-search-departamento"), body: document.getElementById("ua-liderados-linked-body"), footer: document.getElementById("ua-liderados-linked-footer"), acaoBtn: document.getElementById("ua-liderados-linked-unlink-btn"), }, secundariaValor: departamentosTexto, }); 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"; } // Compara duas strings tratando conteúdo numérico como número (ex.: // "92" < "150") e caindo pra comparação alfabética quando não são os dois // numéricos — mesmo critério de comparaValoresLista() em // importacao-plano-saude.js. function comparaValoresUsuario(a, b) { const an = Number(a); const bn = Number(b); const ambosNumeros = a !== "" && b !== "" && !Number.isNaN(an) && !Number.isNaN(bn); if (ambosNumeros) return an - bn; return String(a).localeCompare(String(b), "pt-BR", { sensitivity: "base" }); } const PID_UA_SORT_VALOR = { username: (u) => u.username || "", nome: (u) => u.nome || "", codigo_folha: (u) => u.codigo_folha || "", codigo_questor: (u) => u.codigo_questor || "", codigo_tareffa: (u) => u.codigo_tareffa || "", codigo_contabit: (u) => u.codigo_contabit || "", ramal: (u) => u.ramal || "", status: (u) => (u.is_active ? 1 : 0), }; // Célula vazia fica destacada (itálico, cor apagada) — ordenar a coluna // agrupa quem ainda não tem aquele código/ramal cadastrado. function celulaCodigo(valor) { return valor ? pidEscapeHtml(valor) : '—'; } function renderList() { const term = searchInput.value.trim().toLowerCase(); const extrator = PID_UA_SORT_VALOR[sortKey]; 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) => comparaValoresUsuario(extrator(a), extrator(b)) * sortDir); sortHeaders.forEach((th) => { const key = th.getAttribute("data-sort"); th.classList.toggle("is-sort-asc", key === sortKey && sortDir === 1); th.classList.toggle("is-sort-desc", key === sortKey && sortDir === -1); }); tableBody.innerHTML = rows.length ? rows .map( (u) => ` ${pidEscapeHtml(u.username)} ${pidEscapeHtml(u.nome)} ${celulaCodigo(u.codigo_folha)} ${celulaCodigo(u.codigo_questor)} ${celulaCodigo(u.codigo_tareffa)} ${celulaCodigo(u.codigo_contabit)} ${celulaCodigo(u.ramal)} ${pidEscapeHtml(profileNomes(u.perfis))} ${u.is_active ? "Ativo" : "Inativo"}
` ) .join("") : `Nenhum usuário encontrado.`; 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) => ` ` ) .join(""); } function fillLideradosChecklist(selectedIds, excludeId) { // Usuário inativo não entra como candidato (nem aparece do lado // vinculado) — mesma decisão do widget "Usuários do Escritório" de // Perfis de Acesso (ver profiles.js). const candidatos = users .filter((u) => u.id !== excludeId && u.is_active) .slice() .sort((a, b) => a.nome.localeCompare(b.nome)); seletorLiderados.setDados(candidatos, selectedIds); } function selectedLideradosIds() { return seletorLiderados.getVinculadosIds(); } let departamentosCache = []; async function fillDepartamentoChecklist(selectedIds) { departamentosCache = (await pidFetchDepartamentos()).slice().sort((a, b) => a.nome.localeCompare(b.nome)); departamentoChecklist.innerHTML = departamentosCache .map( (d) => `
` ) .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); sortHeaders.forEach((th) => { th.addEventListener("click", () => { const key = th.getAttribute("data-sort"); if (sortKey === key) { sortDir *= -1; } else { sortKey = key; sortDir = 1; } 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 (!(await pidConfirm(`Excluir o departamento "${dep.nome}"?`, { perigoso: true }))) return; try { await pidDeleteDepartamento(id); const selected = selectedDepartamentoIds().filter((depId) => depId !== id); await fillDepartamentoChecklist(selected); } catch (e) { await pidAlert(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) { await pidAlert("Você não pode excluir o usuário com o qual está logado."); return; } const account = users.find((u) => u.id === id); if (account && (await pidConfirm(`Excluir o usuário "${account.nome}"?`, { perigoso: true }))) { 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) { await pidAlert("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 (await pidConfirm(`${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(); });