const PID_AG_ROW_ICON = ''; const PID_AG_IMAGE_MAX_BYTES = 2 * 1024 * 1024; async function pidFetchAcessoGeralSecoes() { return (await pidApiRequest("/acessos-gerais-secoes/")) || []; } async function pidCreateAcessoGeralSecao(payload) { return pidApiRequest("/acessos-gerais-secoes/", { method: "POST", body: payload }); } async function pidUpdateAcessoGeralSecao(id, payload) { return pidApiRequest(`/acessos-gerais-secoes/${id}/`, { method: "PATCH", body: payload }); } async function pidDeleteAcessoGeralSecao(id) { return pidApiRequest(`/acessos-gerais-secoes/${id}/`, { method: "DELETE" }); } async function pidFetchAcessosGerais() { return (await pidApiRequest("/acessos-gerais/")) || []; } async function pidCreateAcessoGeral(payload) { return pidApiRequest("/acessos-gerais/", { method: "POST", body: payload }); } async function pidUpdateAcessoGeral(id, payload) { return pidApiRequest(`/acessos-gerais/${id}/`, { method: "PATCH", body: payload }); } async function pidDeleteAcessoGeral(id) { return pidApiRequest(`/acessos-gerais/${id}/`, { method: "DELETE" }); } document.addEventListener("DOMContentLoaded", async () => { const sectionsEl = document.getElementById("ag-sections"); if (!sectionsEl) return; const me = await pidGetMe(); if (!me) return; const agPerms = (me.permissoes_efetivas && me.permissoes_efetivas["links-ferramentas"]) || {}; const canView = !!(agPerms.apps && agPerms.apps["acessos-gerais-visualizar"]); const canManage = !!(agPerms.apps && agPerms.apps["acessos-gerais-editar"]); const noAccessEl = document.getElementById("ag-no-access"); const contentEl = document.getElementById("ag-content"); if (noAccessEl) noAccessEl.hidden = canView; if (contentEl) contentEl.hidden = !canView; if (!canView) return; const emptyEl = document.getElementById("ag-empty"); const addSecaoBtn = document.getElementById("ag-add-secao-btn"); if (addSecaoBtn) addSecaoBtn.hidden = !canManage; // Modal de seção (adicionar/renomear) const secaoModal = document.getElementById("ag-secao-modal"); const secaoFormTitle = document.getElementById("ag-secao-form-title"); const secaoFormNome = document.getElementById("ag-secao-form-nome"); const secaoFormPerfis = document.getElementById("ag-secao-form-perfis"); const secaoFormError = document.getElementById("ag-secao-form-error"); const secaoSaveBtn = document.getElementById("ag-secao-form-save-btn"); const secaoCancelBtn = document.getElementById("ag-secao-form-cancel-btn"); // Modal de acesso (adicionar/editar) const formModal = document.getElementById("ag-form-modal"); const formTitle = document.getElementById("ag-form-title"); const formNome = document.getElementById("ag-form-nome"); const formUrl = document.getElementById("ag-form-url"); const formUsuario = document.getElementById("ag-form-usuario"); const formSenha = document.getElementById("ag-form-senha"); const formSenhaToggle = document.getElementById("ag-form-senha-toggle"); const formObservacoes = document.getElementById("ag-form-observacoes"); const formError = document.getElementById("ag-form-error"); const formSaveBtn = document.getElementById("ag-form-save-btn"); const formCancelBtn = document.getElementById("ag-form-cancel-btn"); // Modal de visualização (popup ao clicar numa linha) const viewModal = document.getElementById("ag-view-modal"); const viewNome = document.getElementById("ag-view-nome"); const viewUrlField = document.getElementById("ag-view-url-field"); const viewUrl = document.getElementById("ag-view-url"); const viewUsuarioField = document.getElementById("ag-view-usuario-field"); const viewUsuario = document.getElementById("ag-view-usuario"); const viewSenhaField = document.getElementById("ag-view-senha-field"); const viewSenha = document.getElementById("ag-view-senha"); const viewSenhaToggle = document.getElementById("ag-view-senha-toggle"); const viewObservacoesField = document.getElementById("ag-view-observacoes-field"); const viewObservacoes = document.getElementById("ag-view-observacoes"); const viewActions = document.getElementById("ag-view-actions"); const viewEditBtn = document.getElementById("ag-view-edit-btn"); const viewDeleteBtn = document.getElementById("ag-view-delete-btn"); const viewCloseBtn = document.getElementById("ag-view-close-btn"); pidBindPasswordToggle(formSenhaToggle, formSenha); let secoes = []; let acessos = []; let editingSecaoId = null; let editingAcessoId = null; let viewingAcessoId = null; function acessosDaSecao(secaoId) { return acessos.filter((a) => a.secao === secaoId); } function rowHtml(acesso, index, total) { const adminHtml = canManage ? ` ` : ""; return `
  • ${adminHtml}
  • `; } function sectionHtml(secao) { const rows = acessosDaSecao(secao.id); const rowsHtml = rows.length ? rows.map((a, index) => rowHtml(a, index, rows.length)).join("") : `
  • Nenhum acesso cadastrado nesta seção.
  • `; const actionsHtml = canManage ? `
    ` : ""; const addRowBtnHtml = canManage ? `` : ""; return `

    ${secao.nome}

    ${actionsHtml}
    ${addRowBtnHtml}
    `; } function render() { sectionsEl.innerHTML = secoes.map(sectionHtml).join(""); if (emptyEl) emptyEl.hidden = secoes.length !== 0; } async function refresh() { try { const [fetchedSecoes, fetchedAcessos] = await Promise.all([ pidFetchAcessoGeralSecoes(), pidFetchAcessosGerais(), ]); secoes = fetchedSecoes; acessos = fetchedAcessos; } catch (e) { secoes = []; acessos = []; } render(); } // --- Modal de seção --- let perfisCache = []; async function ensurePerfisCache() { if (!perfisCache.length) { perfisCache = (await pidFetchPerfis()).slice().sort((a, b) => a.nome.localeCompare(b.nome)); } } function renderSecaoPerfisChecklist(selectedCodigos) { secaoFormPerfis.innerHTML = perfisCache .map( (p) => ` ` ) .join(""); } function selectedSecaoPerfis() { return Array.from(secaoFormPerfis.querySelectorAll('input[type="checkbox"]:checked')).map((cb) => Number(cb.value)); } async function openSecaoModal(secao) { editingSecaoId = secao ? secao.id : null; secaoFormTitle.textContent = secao ? "Renomear Seção" : "Adicionar Seção"; secaoFormNome.value = secao ? secao.nome : ""; secaoFormError.textContent = ""; secaoSaveBtn.textContent = secao ? "Salvar" : "Adicionar"; await ensurePerfisCache(); renderSecaoPerfisChecklist(secao ? secao.perfis_restritos : []); secaoModal.hidden = false; secaoFormNome.focus(); } function closeSecaoModal() { secaoModal.hidden = true; editingSecaoId = null; } if (addSecaoBtn) addSecaoBtn.addEventListener("click", () => openSecaoModal(null)); if (secaoCancelBtn) secaoCancelBtn.addEventListener("click", closeSecaoModal); if (secaoModal) { secaoModal.addEventListener("click", (event) => { if (event.target === secaoModal) closeSecaoModal(); }); } if (secaoSaveBtn) { secaoSaveBtn.addEventListener("click", async () => { const nome = secaoFormNome.value.trim(); if (!nome) { secaoFormError.textContent = "Informe o nome da seção."; return; } secaoFormError.textContent = ""; const payload = { nome, perfis_restritos: selectedSecaoPerfis() }; try { if (editingSecaoId) { await pidUpdateAcessoGeralSecao(editingSecaoId, payload); } else { await pidCreateAcessoGeralSecao(payload); } closeSecaoModal(); await refresh(); } catch (e) { secaoFormError.textContent = e.message; } }); } // --- Modal de acesso (adicionar/editar) --- let formSecaoId = null; function openFormModal(secaoId, acesso) { formSecaoId = secaoId; editingAcessoId = acesso ? acesso.id : null; formTitle.textContent = acesso ? "Editar Acesso" : "Adicionar Acesso"; formNome.value = acesso ? acesso.nome : ""; formUrl.value = acesso ? acesso.url : ""; formUsuario.value = acesso ? acesso.usuario : ""; formSenha.value = acesso ? acesso.senha : ""; formSenha.type = "password"; formObservacoes.innerHTML = acesso ? acesso.observacoes : ""; formError.textContent = ""; formSaveBtn.textContent = acesso ? "Salvar" : "Adicionar"; formModal.hidden = false; formNome.focus(); } // --- Observações ricas (contenteditable + imagens embutidas via colar/arrastar) --- function observacoesValue() { const hasImage = formObservacoes.querySelector("img") !== null; const hasText = formObservacoes.textContent.trim() !== ""; return hasImage || hasText ? formObservacoes.innerHTML : ""; } function placeCaretAtPoint(x, y) { const selection = window.getSelection(); if (document.caretRangeFromPoint) { const range = document.caretRangeFromPoint(x, y); if (range) { selection.removeAllRanges(); selection.addRange(range); } } else if (document.caretPositionFromPoint) { const pos = document.caretPositionFromPoint(x, y); if (pos) { const range = document.createRange(); range.setStart(pos.offsetNode, pos.offset); range.collapse(true); selection.removeAllRanges(); selection.addRange(range); } } } function insertImageFile(file, atPoint) { if (!file || !file.type.startsWith("image/")) return; if (file.size > PID_AG_IMAGE_MAX_BYTES) { formError.textContent = "A imagem deve ter no máximo 2MB."; return; } formError.textContent = ""; const reader = new FileReader(); reader.onload = () => { formObservacoes.focus(); if (atPoint) placeCaretAtPoint(atPoint.x, atPoint.y); document.execCommand("insertImage", false, reader.result); }; reader.readAsDataURL(file); } if (formObservacoes) { formObservacoes.addEventListener("paste", (event) => { const items = Array.from((event.clipboardData && event.clipboardData.items) || []); const imageItem = items.find((item) => item.type.startsWith("image/")); if (!imageItem) return; event.preventDefault(); insertImageFile(imageItem.getAsFile()); }); formObservacoes.addEventListener("dragover", (event) => { event.preventDefault(); }); formObservacoes.addEventListener("drop", (event) => { const file = event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]; if (!file || !file.type.startsWith("image/")) return; event.preventDefault(); insertImageFile(file, { x: event.clientX, y: event.clientY }); }); } function closeFormModal() { formModal.hidden = true; editingAcessoId = null; formSecaoId = null; } if (formCancelBtn) formCancelBtn.addEventListener("click", closeFormModal); if (formModal) { formModal.addEventListener("click", (event) => { if (event.target === formModal) closeFormModal(); }); } if (formSaveBtn) { formSaveBtn.addEventListener("click", async () => { const nome = formNome.value.trim(); if (!nome) { formError.textContent = "Informe o nome do acesso."; return; } formError.textContent = ""; const payload = { nome, url: formUrl.value.trim(), usuario: formUsuario.value.trim(), senha: formSenha.value, observacoes: observacoesValue(), }; if (!editingAcessoId) payload.secao = formSecaoId; try { if (editingAcessoId) { await pidUpdateAcessoGeral(editingAcessoId, payload); } else { await pidCreateAcessoGeral(payload); } closeFormModal(); await refresh(); } catch (e) { formError.textContent = e.message; } }); } // --- Modal de visualização (popup) --- function setViewField(fieldEl, valueEl, value) { if (!fieldEl) return; fieldEl.hidden = !value; if (valueEl) valueEl.textContent = value || ""; } function openViewModal(acesso) { viewingAcessoId = acesso.id; viewNome.textContent = acesso.nome; if (viewUrlField) { viewUrlField.hidden = !acesso.url; if (viewUrl) { viewUrl.textContent = acesso.url; viewUrl.href = acesso.url; } } setViewField(viewUsuarioField, viewUsuario, acesso.usuario); if (viewSenhaField) { viewSenhaField.hidden = !acesso.senha; if (viewSenha) { viewSenha.dataset.value = acesso.senha || ""; viewSenha.textContent = "••••••••"; } if (viewSenhaToggle) viewSenhaToggle.setAttribute("aria-label", "Mostrar senha"); } if (viewObservacoesField) { viewObservacoesField.hidden = !acesso.observacoes; if (viewObservacoes) viewObservacoes.innerHTML = acesso.observacoes || ""; } if (viewActions) viewActions.hidden = !canManage; viewModal.hidden = false; } function closeViewModal() { viewModal.hidden = true; viewingAcessoId = null; } if (viewCloseBtn) viewCloseBtn.addEventListener("click", closeViewModal); if (viewModal) { viewModal.addEventListener("click", (event) => { if (event.target === viewModal) closeViewModal(); }); } if (viewSenhaToggle) { viewSenhaToggle.addEventListener("click", () => { const revealed = viewSenha.textContent === viewSenha.dataset.value; viewSenha.textContent = revealed ? "••••••••" : viewSenha.dataset.value; viewSenhaToggle.setAttribute("aria-label", revealed ? "Mostrar senha" : "Ocultar senha"); }); } if (viewEditBtn) { viewEditBtn.addEventListener("click", () => { const acesso = acessos.find((a) => a.id === viewingAcessoId); if (!acesso) return; closeViewModal(); openFormModal(acesso.secao, acesso); }); } if (viewDeleteBtn) { viewDeleteBtn.addEventListener("click", async () => { const acesso = acessos.find((a) => a.id === viewingAcessoId); if (!acesso) return; if (!(await pidConfirm(`Excluir o acesso "${acesso.nome}"?`, { perigoso: true }))) return; await pidDeleteAcessoGeral(acesso.id); closeViewModal(); await refresh(); }); } // --- Ações na grade (seções e linhas) --- async function swapOrdem(a, b) { await Promise.all([pidUpdateAcessoGeral(a.id, { ordem: b.ordem }), pidUpdateAcessoGeral(b.id, { ordem: a.ordem })]); await refresh(); } sectionsEl.addEventListener("click", async (event) => { const secaoEditBtn = event.target.closest("[data-secao-edit]"); if (secaoEditBtn) { const id = Number(secaoEditBtn.getAttribute("data-secao-edit")); const secao = secoes.find((s) => s.id === id); if (secao) openSecaoModal(secao); return; } const secaoDeleteBtn = event.target.closest("[data-secao-delete]"); if (secaoDeleteBtn) { const id = Number(secaoDeleteBtn.getAttribute("data-secao-delete")); const secao = secoes.find((s) => s.id === id); if (secao && (await pidConfirm(`Excluir a seção "${secao.nome}" e todos os seus acessos?`, { perigoso: true }))) { await pidDeleteAcessoGeralSecao(id); await refresh(); } return; } const addRowBtn = event.target.closest("[data-secao-add]"); if (addRowBtn) { const secaoId = Number(addRowBtn.getAttribute("data-secao-add")); openFormModal(secaoId, null); return; } const openBtn = event.target.closest("[data-acesso-open]"); if (openBtn) { const id = Number(openBtn.getAttribute("data-acesso-open")); const acesso = acessos.find((a) => a.id === id); if (acesso) openViewModal(acesso); return; } const upBtn = event.target.closest("[data-acesso-move-up]"); if (upBtn) { const id = Number(upBtn.getAttribute("data-acesso-move-up")); const acesso = acessos.find((a) => a.id === id); if (!acesso) return; const irmaos = acessosDaSecao(acesso.secao); const index = irmaos.findIndex((a) => a.id === id); if (index > 0) await swapOrdem(irmaos[index], irmaos[index - 1]); return; } const downBtn = event.target.closest("[data-acesso-move-down]"); if (downBtn) { const id = Number(downBtn.getAttribute("data-acesso-move-down")); const acesso = acessos.find((a) => a.id === id); if (!acesso) return; const irmaos = acessosDaSecao(acesso.secao); const index = irmaos.findIndex((a) => a.id === id); if (index !== -1 && index < irmaos.length - 1) await swapOrdem(irmaos[index], irmaos[index + 1]); } }); // --- Drag-and-drop (reordenar linhas dentro da mesma seção) --- let draggedId = null; let dropTargetId = null; let dropBefore = true; function clearDropHighlight() { sectionsEl.querySelectorAll(".ag-row--drop-before, .ag-row--drop-after").forEach((el) => { el.classList.remove("ag-row--drop-before", "ag-row--drop-after"); }); } sectionsEl.addEventListener("dragstart", (event) => { const row = event.target.closest(".ag-row--draggable"); if (!row) return; draggedId = Number(row.dataset.acessoId); event.dataTransfer.effectAllowed = "move"; event.dataTransfer.setData("text/plain", String(draggedId)); requestAnimationFrame(() => row.classList.add("ag-row--dragging")); }); sectionsEl.addEventListener("dragover", (event) => { if (draggedId === null) return; const row = event.target.closest(".ag-row--draggable"); if (!row || Number(row.dataset.acessoId) === draggedId) return; const draggedAcesso = acessos.find((a) => a.id === draggedId); const targetAcesso = acessos.find((a) => a.id === Number(row.dataset.acessoId)); if (!draggedAcesso || !targetAcesso || draggedAcesso.secao !== targetAcesso.secao) return; event.preventDefault(); const rect = row.getBoundingClientRect(); dropBefore = event.clientY < rect.top + rect.height / 2; dropTargetId = Number(row.dataset.acessoId); clearDropHighlight(); row.classList.add(dropBefore ? "ag-row--drop-before" : "ag-row--drop-after"); }); sectionsEl.addEventListener("drop", async (event) => { if (draggedId === null) return; event.preventDefault(); clearDropHighlight(); const fromId = draggedId; const toId = dropTargetId; draggedId = null; dropTargetId = null; if (toId === null || fromId === toId) return; const fromAcesso = acessos.find((a) => a.id === fromId); if (!fromAcesso) return; const irmaos = acessosDaSecao(fromAcesso.secao); const fromIndex = irmaos.findIndex((a) => a.id === fromId); if (fromIndex === -1) return; const reordered = irmaos.slice(); const [moved] = reordered.splice(fromIndex, 1); let insertIndex = reordered.findIndex((a) => a.id === toId); if (insertIndex === -1) return; if (!dropBefore) insertIndex += 1; reordered.splice(insertIndex, 0, moved); const updates = reordered.map((acesso, index) => ({ acesso, index })).filter(({ acesso, index }) => acesso.ordem !== index); if (!updates.length) return; acessos = acessos.map((a) => { const update = updates.find((u) => u.acesso.id === a.id); return update ? { ...a, ordem: update.index } : a; }); render(); await Promise.all(updates.map(({ acesso, index }) => pidUpdateAcessoGeral(acesso.id, { ordem: index }))); await refresh(); }); sectionsEl.addEventListener("dragend", () => { draggedId = null; dropTargetId = null; clearDropHighlight(); sectionsEl.querySelectorAll(".ag-row--dragging").forEach((el) => el.classList.remove("ag-row--dragging")); }); await refresh(); });