982 lines
38 KiB
JavaScript
982 lines
38 KiB
JavaScript
const PID_RAMAL_CHAMADO_URL =
|
|
"https://depaula-tvcorporativa.lovable.app/chamar?token=4d23bd63-24ff-4d49-9ae9-c87919a71262";
|
|
|
|
async function pidFetchRamais() {
|
|
return (await pidApiRequest("/ramais/")) || [];
|
|
}
|
|
|
|
async function pidFetchUsuariosDisponiveisParaRamal() {
|
|
return (await pidApiRequest("/ramais/usuarios/")) || [];
|
|
}
|
|
|
|
async function pidCreateRamal(payload) {
|
|
return pidApiRequest("/ramais/", { method: "POST", body: payload });
|
|
}
|
|
|
|
async function pidUpdateRamal(id, payload) {
|
|
return pidApiRequest(`/ramais/${id}/`, { method: "PATCH", body: payload });
|
|
}
|
|
|
|
async function pidUpdateRamalUsuario(usuarioId, numero) {
|
|
return pidApiRequest(`/ramais/usuarios/${usuarioId}/`, { method: "PATCH", body: { numero } });
|
|
}
|
|
|
|
async function pidDeleteRamal(id) {
|
|
return pidApiRequest(`/ramais/${id}/`, { method: "DELETE" });
|
|
}
|
|
|
|
async function pidCreateRamalAusencia(payload) {
|
|
return pidApiRequest("/ramais-ausencias/", { method: "POST", body: payload });
|
|
}
|
|
|
|
async function pidFetchRamalAusencia(id) {
|
|
return pidApiRequest(`/ramais-ausencias/${id}/`);
|
|
}
|
|
|
|
async function pidUpdateRamalAusencia(id, payload) {
|
|
return pidApiRequest(`/ramais-ausencias/${id}/`, { method: "PATCH", body: payload });
|
|
}
|
|
|
|
async function pidDeleteRamalAusencia(id) {
|
|
return pidApiRequest(`/ramais-ausencias/${id}/`, { method: "DELETE" });
|
|
}
|
|
|
|
async function pidFetchTelefonesExternos() {
|
|
return (await pidApiRequest("/telefones-externos/")) || [];
|
|
}
|
|
|
|
async function pidCreateTelefoneExterno(payload) {
|
|
return pidApiRequest("/telefones-externos/", { method: "POST", body: payload });
|
|
}
|
|
|
|
async function pidUpdateTelefoneExterno(id, payload) {
|
|
return pidApiRequest(`/telefones-externos/${id}/`, { method: "PATCH", body: payload });
|
|
}
|
|
|
|
async function pidDeleteTelefoneExterno(id) {
|
|
return pidApiRequest(`/telefones-externos/${id}/`, { method: "DELETE" });
|
|
}
|
|
|
|
async function pidFetchFuncoesTelefonia() {
|
|
return (await pidApiRequest("/funcoes-telefonia/")) || [];
|
|
}
|
|
|
|
async function pidCreateFuncaoTelefonia(payload) {
|
|
return pidApiRequest("/funcoes-telefonia/", { method: "POST", body: payload });
|
|
}
|
|
|
|
async function pidUpdateFuncaoTelefonia(id, payload) {
|
|
return pidApiRequest(`/funcoes-telefonia/${id}/`, { method: "PATCH", body: payload });
|
|
}
|
|
|
|
async function pidDeleteFuncaoTelefonia(id) {
|
|
return pidApiRequest(`/funcoes-telefonia/${id}/`, { method: "DELETE" });
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
const tableBody = document.getElementById("ram-table-body");
|
|
if (!tableBody) return;
|
|
|
|
const me = await pidGetMe();
|
|
if (!me) return;
|
|
|
|
// Cada subtela (aba) de Ramais tem sua própria permissão dedicada — só as 3 com
|
|
// conteúdo administrável (Ramais, Telefones Externos, Funções de Telefonia) têm
|
|
// "editar"; Responsável no Tareffa/Férias só têm "visualizar" (são placeholders).
|
|
const ramalApps = (me.permissoes_efetivas && me.permissoes_efetivas["ramais"] && me.permissoes_efetivas["ramais"].apps) || {};
|
|
|
|
const ramaisCanView = !!ramalApps["ramais-visualizar"];
|
|
const ramaisCanManage = !!ramalApps["ramais-editar"];
|
|
const responsavelTareffaCanView = !!ramalApps["responsavel-tareffa-visualizar"];
|
|
const telCanView = !!ramalApps["telefones-externos-visualizar"];
|
|
const telCanManage = !!ramalApps["telefones-externos-editar"];
|
|
const feriasCanView = !!ramalApps["ferias-visualizar"];
|
|
const funCanView = !!ramalApps["funcoes-telefonia-visualizar"];
|
|
const funCanManage = !!ramalApps["funcoes-telefonia-editar"];
|
|
|
|
const RAM_TAB_ORDER = ["ramais", "responsavel-tareffa", "telefones-externos", "ferias", "funcoes-telefonia"];
|
|
const ramTabViewPerms = {
|
|
ramais: ramaisCanView,
|
|
"responsavel-tareffa": responsavelTareffaCanView,
|
|
"telefones-externos": telCanView,
|
|
ferias: feriasCanView,
|
|
"funcoes-telefonia": funCanView,
|
|
};
|
|
const canViewAnyRamTab = RAM_TAB_ORDER.some((key) => ramTabViewPerms[key]);
|
|
|
|
const noAccessEl = document.getElementById("ramais-no-access");
|
|
const contentEl = document.getElementById("ramais-content");
|
|
if (noAccessEl) noAccessEl.hidden = canViewAnyRamTab;
|
|
if (contentEl) contentEl.hidden = !canViewAnyRamTab;
|
|
if (!canViewAnyRamTab) return;
|
|
|
|
const searchNomeInput = document.getElementById("ram-search-nome");
|
|
const searchDepartamentoInput = document.getElementById("ram-search-departamento");
|
|
const footerCount = document.getElementById("ram-footer-count");
|
|
|
|
const addBtn = document.getElementById("ram-add-btn");
|
|
const ausenciaBtn = document.getElementById("ram-ausencia-btn");
|
|
const chamadoBtn = document.getElementById("ram-chamado-btn");
|
|
if (addBtn) addBtn.hidden = !ramaisCanManage;
|
|
if (ausenciaBtn) ausenciaBtn.hidden = !ramaisCanManage;
|
|
|
|
let activeRamTab = RAM_TAB_ORDER.find((key) => ramTabViewPerms[key]) || "ramais";
|
|
|
|
function renderRamTabs() {
|
|
document.querySelectorAll("[data-ram-tab]").forEach((tab) => {
|
|
const key = tab.getAttribute("data-ram-tab");
|
|
tab.hidden = !ramTabViewPerms[key];
|
|
tab.classList.toggle("is-active", key === activeRamTab);
|
|
});
|
|
document.querySelectorAll("[data-ram-tab-panel]").forEach((panel) => {
|
|
panel.hidden = panel.getAttribute("data-ram-tab-panel") !== activeRamTab;
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll("[data-ram-tab]").forEach((tab) => {
|
|
tab.addEventListener("click", () => {
|
|
activeRamTab = tab.getAttribute("data-ram-tab");
|
|
renderRamTabs();
|
|
});
|
|
});
|
|
|
|
// Telefones Externos
|
|
const telSearchNomeInput = document.getElementById("ram-tel-search-nome");
|
|
const telSearchObsInput = document.getElementById("ram-tel-search-obs");
|
|
const telFooterCount = document.getElementById("ram-tel-footer-count");
|
|
const telTableBody = document.getElementById("ram-tel-table-body");
|
|
const telAddBtn = document.getElementById("ram-tel-add-btn");
|
|
if (telAddBtn) telAddBtn.hidden = !telCanManage;
|
|
|
|
const telFormModal = document.getElementById("telefone-externo-form-modal");
|
|
const telFormTitle = document.getElementById("tel-form-title");
|
|
const telFormNome = document.getElementById("tel-form-nome");
|
|
const telFormRamal = document.getElementById("tel-form-ramal");
|
|
const telFormTelefone = document.getElementById("tel-form-telefone");
|
|
const telFormObservacoes = document.getElementById("tel-form-observacoes");
|
|
const telFormError = document.getElementById("tel-form-error");
|
|
const telFormSaveBtn = document.getElementById("tel-form-save-btn");
|
|
const telFormCancelBtn = document.getElementById("tel-form-cancel-btn");
|
|
|
|
let telefonesExternos = [];
|
|
let editingTelefoneId = null;
|
|
|
|
// Funções de Telefonia
|
|
const funTableBody = document.getElementById("ram-fun-table-body");
|
|
const funFooterCount = document.getElementById("ram-fun-footer-count");
|
|
const funAddBtn = document.getElementById("ram-fun-add-btn");
|
|
if (funAddBtn) funAddBtn.hidden = !funCanManage;
|
|
|
|
const funFormModal = document.getElementById("funcao-telefonia-form-modal");
|
|
const funFormTitle = document.getElementById("fun-form-title");
|
|
const funFormComando = document.getElementById("fun-form-comando");
|
|
const funFormFuncao = document.getElementById("fun-form-funcao");
|
|
const funFormResumo = document.getElementById("fun-form-resumo");
|
|
const funFormError = document.getElementById("fun-form-error");
|
|
const funFormSaveBtn = document.getElementById("fun-form-save-btn");
|
|
const funFormCancelBtn = document.getElementById("fun-form-cancel-btn");
|
|
|
|
let funcoesTelefonia = [];
|
|
let editingFuncaoId = null;
|
|
|
|
const formModal = document.getElementById("ramal-form-modal");
|
|
const formTitle = document.getElementById("ram-form-title");
|
|
const formSubtitle = document.getElementById("ram-form-subtitle");
|
|
const formNumero = document.getElementById("ram-form-numero");
|
|
const formNome = document.getElementById("ram-form-nome");
|
|
const formDepartamento = document.getElementById("ram-form-departamento");
|
|
const formError = document.getElementById("ram-form-error");
|
|
const formSaveBtn = document.getElementById("ram-form-save-btn");
|
|
const formCancelBtn = document.getElementById("ram-form-cancel-btn");
|
|
|
|
const ausenciaModal = document.getElementById("ramal-ausencia-modal");
|
|
const ausUsuario = document.getElementById("ram-aus-usuario");
|
|
const ausDataInicio = document.getElementById("ram-aus-data-inicio");
|
|
const ausHoraInicio = document.getElementById("ram-aus-hora-inicio");
|
|
const ausDataFim = document.getElementById("ram-aus-data-fim");
|
|
const ausHoraVolta = document.getElementById("ram-aus-hora-volta");
|
|
const ausTipo = document.getElementById("ram-aus-tipo");
|
|
const ausObservacoes = document.getElementById("ram-aus-observacoes");
|
|
const ausError = document.getElementById("ram-aus-error");
|
|
const ausSaveBtn = document.getElementById("ram-aus-save-btn");
|
|
const ausCancelBtn = document.getElementById("ram-aus-cancel-btn");
|
|
|
|
const ausViewModal = document.getElementById("ramal-ausencia-view-modal");
|
|
const ausViewTitle = document.getElementById("ram-aus-view-title");
|
|
const ausViewCloseBtn = document.getElementById("ram-aus-view-close-btn");
|
|
const ausViewUsuario = document.getElementById("ram-aus-view-usuario");
|
|
const ausViewDataInicio = document.getElementById("ram-aus-view-data-inicio");
|
|
const ausViewHoraInicio = document.getElementById("ram-aus-view-hora-inicio");
|
|
const ausViewDataFim = document.getElementById("ram-aus-view-data-fim");
|
|
const ausViewHoraVolta = document.getElementById("ram-aus-view-hora-volta");
|
|
const ausViewTipo = document.getElementById("ram-aus-view-tipo");
|
|
const ausViewObservacoes = document.getElementById("ram-aus-view-observacoes");
|
|
const ausViewError = document.getElementById("ram-aus-view-error");
|
|
const ausViewActions = document.getElementById("ram-aus-view-actions");
|
|
const ausEditActions = document.getElementById("ram-aus-edit-actions");
|
|
const ausViewEditBtn = document.getElementById("ram-aus-view-edit-btn");
|
|
const ausViewDeleteBtn = document.getElementById("ram-aus-view-delete-btn");
|
|
const ausViewCancelEditBtn = document.getElementById("ram-aus-view-cancel-edit-btn");
|
|
const ausViewSaveBtn = document.getElementById("ram-aus-view-save-btn");
|
|
|
|
const chamadoModal = document.getElementById("ramal-chamado-modal");
|
|
const chamadoIframe = document.getElementById("ram-chamado-iframe");
|
|
const chamadoCloseBtn = document.getElementById("ram-chamado-close-btn");
|
|
|
|
const msgErroCarga = "Não foi possível carregar. Recarregue a página; se persistir, contate a Inovação.";
|
|
|
|
// Trava o botão (e troca o texto) enquanto a requisição roda, contra
|
|
// clique duplo; restaura no fim, com sucesso ou erro.
|
|
async function comTrava(botao, textoOcupado, acao) {
|
|
const textoOriginal = botao.textContent;
|
|
botao.disabled = true;
|
|
if (textoOcupado) botao.textContent = textoOcupado;
|
|
try {
|
|
return await acao();
|
|
} finally {
|
|
botao.disabled = false;
|
|
if (textoOcupado) botao.textContent = textoOriginal;
|
|
}
|
|
}
|
|
|
|
// Retrato dos valores de um formulário na abertura, pra só perguntar
|
|
// "Sair sem salvar" quando algo mudou de fato.
|
|
function retratoCampos(campos) {
|
|
return JSON.stringify(campos.map((campo) => campo.value));
|
|
}
|
|
|
|
async function podeDescartar(retrato, campos) {
|
|
if (retrato === null || retratoCampos(campos) === retrato) return true;
|
|
return pidConfirm("Sair sem salvar as alterações?", {
|
|
titulo: "Sair sem salvar",
|
|
textoConfirmar: "Sair sem salvar",
|
|
perigoso: true,
|
|
});
|
|
}
|
|
|
|
let ramais = [];
|
|
let usuariosDisponiveis = [];
|
|
let viewingAusencia = null;
|
|
let viewingRamalContext = null;
|
|
// "create" (avulso novo) | "avulso" (editando Ramal existente) | "usuario" (só ajusta o número em Usuario.ramal)
|
|
let editingMode = "create";
|
|
let editingId = null;
|
|
|
|
function usuarioOptionsHtml() {
|
|
return usuariosDisponiveis.map((u) => `<option value="${pidEscapeHtml(u.id)}">${pidEscapeHtml(u.nome)}</option>`).join("");
|
|
}
|
|
|
|
function rowHtml(ramal) {
|
|
const ausente = ramal.usuario_ausente;
|
|
const aniversario = ramal.usuario_aniversariante;
|
|
const rowClass = aniversario ? "ram-row--aniversario" : ausente ? "ram-row--ausente" : "";
|
|
|
|
const nomeHtml = aniversario
|
|
? `${pidEscapeHtml(ramal.nome_exibicao)} <span class="ram-badge ram-badge--aniversario">🎉 Feliz Aniversário</span>`
|
|
: pidEscapeHtml(ramal.nome_exibicao);
|
|
|
|
const numeroHtml = ramal.numero_exibicao
|
|
? pidEscapeHtml(ramal.numero_exibicao)
|
|
: `<span class="ram-badge ram-badge--pendente">Pendente</span>`;
|
|
|
|
const ausenteBadgeHtml = ausente
|
|
? `<br/><button type="button" class="ram-badge ram-badge--ausente" data-ram-ver-ausencia="${ramal.usuario_ausencia_ativa_id}">(AUSENTE)</button>`
|
|
: "";
|
|
const ramalHtml = `${numeroHtml}${ausenteBadgeHtml}`;
|
|
|
|
const editBtnHtml = ramaisCanManage
|
|
? `
|
|
<button type="button" class="icon-btn" data-ram-edit="${ramal.id}" aria-label="Editar ramal">
|
|
<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>`
|
|
: "";
|
|
const deleteBtnHtml =
|
|
ramaisCanManage && ramal.tipo === "avulso"
|
|
? `
|
|
<button type="button" class="icon-btn" data-ram-delete="${ramal.avulso_id}" aria-label="Excluir ramal">
|
|
<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>`
|
|
: "";
|
|
|
|
return `
|
|
<tr class="${rowClass}">
|
|
<td>${nomeHtml}</td>
|
|
<td>${pidEscapeHtml(ramal.departamento_exibicao || "—")}</td>
|
|
<td class="pa-num">${ramalHtml}</td>
|
|
<td><div class="pa-row-actions">${editBtnHtml}${deleteBtnHtml}</div></td>
|
|
</tr>`;
|
|
}
|
|
|
|
function render() {
|
|
const termNome = searchNomeInput.value.trim().toLowerCase();
|
|
const termDepartamento = searchDepartamentoInput.value.trim().toLowerCase();
|
|
const rows = ramais.filter((r) => {
|
|
const nome = (r.nome_exibicao || "").toLowerCase();
|
|
const departamento = (r.departamento_exibicao || "").toLowerCase();
|
|
return (!termNome || nome.includes(termNome)) && (!termDepartamento || departamento.includes(termDepartamento));
|
|
});
|
|
|
|
// Lista vazia por filtro não é o mesmo que lista vazia de verdade.
|
|
const vazio = ramais.length ? "Nenhum resultado para o filtro." : "Nenhum ramal cadastrado ainda.";
|
|
tableBody.innerHTML = rows.length
|
|
? rows.map(rowHtml).join("")
|
|
: `<tr class="pa-empty-row"><td colspan="4">${vazio}</td></tr>`;
|
|
|
|
if (footerCount) footerCount.textContent = `${rows.length} ${rows.length === 1 ? "item" : "itens"}`;
|
|
}
|
|
|
|
// Cada aba só busca o que o usuário pode ver (sem permissão a API devolve
|
|
// 403). Falha vira aviso na própria tabela, nunca "nenhum cadastrado".
|
|
async function refresh() {
|
|
if (!ramaisCanView) return;
|
|
try {
|
|
const tasks = [pidFetchRamais()];
|
|
if (ramaisCanManage) tasks.push(pidFetchUsuariosDisponiveisParaRamal());
|
|
const results = await Promise.all(tasks);
|
|
ramais = results[0] || [];
|
|
if (ramaisCanManage) usuariosDisponiveis = results[1] || [];
|
|
} catch (e) {
|
|
tableBody.innerHTML = `<tr class="pa-empty-row"><td colspan="4">${msgErroCarga}</td></tr>`;
|
|
if (footerCount) footerCount.textContent = "";
|
|
return;
|
|
}
|
|
render();
|
|
}
|
|
|
|
function telefoneRowHtml(tel) {
|
|
const editBtnHtml = telCanManage
|
|
? `
|
|
<button type="button" class="icon-btn" data-tel-edit="${tel.id}" aria-label="Editar telefone">
|
|
<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>`
|
|
: "";
|
|
const deleteBtnHtml = telCanManage
|
|
? `
|
|
<button type="button" class="icon-btn" data-tel-delete="${tel.id}" aria-label="Excluir telefone">
|
|
<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>`
|
|
: "";
|
|
|
|
return `
|
|
<tr>
|
|
<td>${pidEscapeHtml(tel.nome)}</td>
|
|
<td class="pa-num">${pidEscapeHtml(tel.ramal || "—")}</td>
|
|
<td class="pa-num">${pidEscapeHtml(tel.telefone || "—")}</td>
|
|
<td>${pidEscapeHtml(tel.observacoes || "—")}</td>
|
|
<td><div class="pa-row-actions">${editBtnHtml}${deleteBtnHtml}</div></td>
|
|
</tr>`;
|
|
}
|
|
|
|
function renderTelefonesExternos() {
|
|
const termNome = telSearchNomeInput.value.trim().toLowerCase();
|
|
const termObs = telSearchObsInput.value.trim().toLowerCase();
|
|
const rows = telefonesExternos.filter((tel) => {
|
|
const nome = (tel.nome || "").toLowerCase();
|
|
const obs = (tel.observacoes || "").toLowerCase();
|
|
return (!termNome || nome.includes(termNome)) && (!termObs || obs.includes(termObs));
|
|
});
|
|
|
|
const vazio = telefonesExternos.length ? "Nenhum resultado para o filtro." : "Nenhum telefone externo cadastrado ainda.";
|
|
telTableBody.innerHTML = rows.length
|
|
? rows.map(telefoneRowHtml).join("")
|
|
: `<tr class="pa-empty-row"><td colspan="5">${vazio}</td></tr>`;
|
|
|
|
if (telFooterCount) telFooterCount.textContent = `${rows.length} ${rows.length === 1 ? "item" : "itens"}`;
|
|
}
|
|
|
|
async function refreshTelefonesExternos() {
|
|
if (!telCanView || !telTableBody) return;
|
|
try {
|
|
telefonesExternos = await pidFetchTelefonesExternos();
|
|
} catch (e) {
|
|
telTableBody.innerHTML = `<tr class="pa-empty-row"><td colspan="5">${msgErroCarga}</td></tr>`;
|
|
if (telFooterCount) telFooterCount.textContent = "";
|
|
return;
|
|
}
|
|
renderTelefonesExternos();
|
|
}
|
|
|
|
const telCampos = [telFormNome, telFormRamal, telFormTelefone, telFormObservacoes];
|
|
let telRetrato = null;
|
|
|
|
function openTelefoneModal(tel) {
|
|
telFormError.textContent = "";
|
|
editingTelefoneId = tel ? tel.id : null;
|
|
telFormTitle.textContent = tel ? "Editar Telefone Externo" : "Adicionar Telefone Externo";
|
|
telFormSaveBtn.textContent = tel ? "Salvar" : "Adicionar";
|
|
telFormNome.value = tel ? tel.nome || "" : "";
|
|
telFormRamal.value = tel ? tel.ramal || "" : "";
|
|
telFormTelefone.value = tel ? tel.telefone || "" : "";
|
|
telFormObservacoes.value = tel ? tel.observacoes || "" : "";
|
|
telRetrato = retratoCampos(telCampos);
|
|
telFormModal.hidden = false;
|
|
}
|
|
|
|
function closeTelefoneModal() {
|
|
telFormModal.hidden = true;
|
|
editingTelefoneId = null;
|
|
telRetrato = null;
|
|
}
|
|
|
|
async function tentarFecharTelefoneModal() {
|
|
if (await podeDescartar(telRetrato, telCampos)) closeTelefoneModal();
|
|
}
|
|
|
|
function funcaoRowHtml(fun) {
|
|
const editBtnHtml = funCanManage
|
|
? `
|
|
<button type="button" class="icon-btn" data-fun-edit="${fun.id}" aria-label="Editar função">
|
|
<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>`
|
|
: "";
|
|
const deleteBtnHtml = funCanManage
|
|
? `
|
|
<button type="button" class="icon-btn" data-fun-delete="${fun.id}" aria-label="Excluir função">
|
|
<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>`
|
|
: "";
|
|
|
|
return `
|
|
<tr>
|
|
<td>${pidEscapeHtml(fun.comando)}</td>
|
|
<td>${pidEscapeHtml(fun.funcao)}</td>
|
|
<td>${pidEscapeHtml(fun.resumo || "—")}</td>
|
|
<td><div class="pa-row-actions">${editBtnHtml}${deleteBtnHtml}</div></td>
|
|
</tr>`;
|
|
}
|
|
|
|
function renderFuncoesTelefonia() {
|
|
funTableBody.innerHTML = funcoesTelefonia.length
|
|
? funcoesTelefonia.map(funcaoRowHtml).join("")
|
|
: `<tr class="pa-empty-row"><td colspan="4">Nenhuma função cadastrada ainda.</td></tr>`;
|
|
|
|
if (funFooterCount) {
|
|
funFooterCount.textContent = `${funcoesTelefonia.length} ${funcoesTelefonia.length === 1 ? "item" : "itens"}`;
|
|
}
|
|
}
|
|
|
|
async function refreshFuncoesTelefonia() {
|
|
if (!funCanView || !funTableBody) return;
|
|
try {
|
|
funcoesTelefonia = await pidFetchFuncoesTelefonia();
|
|
} catch (e) {
|
|
funTableBody.innerHTML = `<tr class="pa-empty-row"><td colspan="4">${msgErroCarga}</td></tr>`;
|
|
if (funFooterCount) funFooterCount.textContent = "";
|
|
return;
|
|
}
|
|
renderFuncoesTelefonia();
|
|
}
|
|
|
|
const funCampos = [funFormComando, funFormFuncao, funFormResumo];
|
|
let funRetrato = null;
|
|
|
|
function openFuncaoModal(fun) {
|
|
funFormError.textContent = "";
|
|
editingFuncaoId = fun ? fun.id : null;
|
|
funFormTitle.textContent = fun ? "Editar Função de Telefonia" : "Adicionar Função de Telefonia";
|
|
funFormSaveBtn.textContent = fun ? "Salvar" : "Adicionar";
|
|
funFormComando.value = fun ? fun.comando || "" : "";
|
|
funFormFuncao.value = fun ? fun.funcao || "" : "";
|
|
funFormResumo.value = fun ? fun.resumo || "" : "";
|
|
funRetrato = retratoCampos(funCampos);
|
|
funFormModal.hidden = false;
|
|
}
|
|
|
|
function closeFuncaoModal() {
|
|
funFormModal.hidden = true;
|
|
editingFuncaoId = null;
|
|
funRetrato = null;
|
|
}
|
|
|
|
async function tentarFecharFuncaoModal() {
|
|
if (await podeDescartar(funRetrato, funCampos)) closeFuncaoModal();
|
|
}
|
|
|
|
const formCampos = [formNome, formDepartamento, formNumero];
|
|
let formRetrato = null;
|
|
|
|
function openFormModal(ramal) {
|
|
formError.textContent = "";
|
|
|
|
if (!ramal) {
|
|
editingMode = "create";
|
|
editingId = null;
|
|
formTitle.textContent = "Adicionar um Novo Ramal";
|
|
formSubtitle.hidden = true;
|
|
formSaveBtn.textContent = "Adicionar";
|
|
formNome.value = "";
|
|
formDepartamento.value = "";
|
|
formNumero.value = "";
|
|
formNome.disabled = false;
|
|
formDepartamento.disabled = false;
|
|
} else if (ramal.tipo === "usuario") {
|
|
editingMode = "usuario";
|
|
editingId = ramal.usuario_id;
|
|
formTitle.textContent = "Editar Ramal";
|
|
formSubtitle.hidden = false;
|
|
formSaveBtn.textContent = "Salvar";
|
|
formNome.value = ramal.nome_exibicao || "";
|
|
formDepartamento.value = ramal.departamento_exibicao || "";
|
|
formNumero.value = ramal.numero_exibicao || "";
|
|
formNome.disabled = true;
|
|
formDepartamento.disabled = true;
|
|
} else {
|
|
editingMode = "avulso";
|
|
editingId = ramal.avulso_id;
|
|
formTitle.textContent = "Editar Ramal";
|
|
formSubtitle.hidden = true;
|
|
formSaveBtn.textContent = "Salvar";
|
|
formNome.value = ramal.nome_exibicao || "";
|
|
formDepartamento.value = ramal.departamento_exibicao || "";
|
|
formNumero.value = ramal.numero_exibicao || "";
|
|
formNome.disabled = false;
|
|
formDepartamento.disabled = false;
|
|
}
|
|
|
|
formRetrato = retratoCampos(formCampos);
|
|
formModal.hidden = false;
|
|
}
|
|
|
|
function closeFormModal() {
|
|
formModal.hidden = true;
|
|
editingId = null;
|
|
formRetrato = null;
|
|
}
|
|
|
|
async function tentarFecharFormModal() {
|
|
if (await podeDescartar(formRetrato, formCampos)) closeFormModal();
|
|
}
|
|
|
|
const ausCampos = [ausUsuario, ausDataInicio, ausHoraInicio, ausDataFim, ausHoraVolta, ausTipo, ausObservacoes];
|
|
let ausRetrato = null;
|
|
|
|
function openAusenciaModal() {
|
|
ausError.textContent = "";
|
|
// Opção vazia obriga a escolher: antes o primeiro nome vinha marcado
|
|
// e dava para salvar a ausência na pessoa errada sem perceber.
|
|
ausUsuario.innerHTML = `<option value="">Selecione…</option>${usuarioOptionsHtml()}`;
|
|
ausUsuario.value = "";
|
|
ausDataInicio.value = "";
|
|
ausHoraInicio.value = "";
|
|
ausDataFim.value = "";
|
|
ausHoraVolta.value = "";
|
|
ausTipo.value = "Ausência";
|
|
ausObservacoes.value = "";
|
|
ausRetrato = retratoCampos(ausCampos);
|
|
ausenciaModal.hidden = false;
|
|
}
|
|
|
|
function closeAusenciaModal() {
|
|
ausenciaModal.hidden = true;
|
|
ausRetrato = null;
|
|
}
|
|
|
|
async function tentarFecharAusenciaModal() {
|
|
if (await podeDescartar(ausRetrato, ausCampos)) closeAusenciaModal();
|
|
}
|
|
|
|
const ausViewCampos = [ausViewDataInicio, ausViewHoraInicio, ausViewDataFim, ausViewHoraVolta, ausViewTipo, ausViewObservacoes];
|
|
// Só existe enquanto a ausência está em modo de edição.
|
|
let ausViewRetrato = null;
|
|
|
|
function fillAusenciaViewFields(ausencia, ramal) {
|
|
ausViewUsuario.innerHTML = `<option>${pidEscapeHtml(ramal.nome_exibicao)} - ${pidEscapeHtml(ramal.departamento_exibicao || "—")} / Ramal: ${pidEscapeHtml(ramal.numero_exibicao || "—")}</option>`;
|
|
ausViewDataInicio.value = ausencia.data_inicio || "";
|
|
ausViewHoraInicio.value = ausencia.hora_inicio ? ausencia.hora_inicio.slice(0, 5) : "";
|
|
ausViewDataFim.value = ausencia.data_fim || "";
|
|
ausViewHoraVolta.value = ausencia.hora_volta ? ausencia.hora_volta.slice(0, 5) : "";
|
|
ausViewTipo.value = ausencia.tipo || "";
|
|
ausViewObservacoes.value = ausencia.observacoes || "";
|
|
}
|
|
|
|
function setAusenciaViewMode(mode) {
|
|
const editing = mode === "edit";
|
|
ausViewDataInicio.disabled = !editing;
|
|
ausViewHoraInicio.disabled = !editing;
|
|
ausViewDataFim.disabled = !editing;
|
|
ausViewHoraVolta.disabled = !editing;
|
|
ausViewTipo.disabled = !editing;
|
|
ausViewObservacoes.disabled = !editing;
|
|
ausViewTitle.textContent = editing ? "Editar Ausência" : "Visualizar Ausência";
|
|
ausViewActions.hidden = editing;
|
|
ausEditActions.hidden = !editing;
|
|
ausViewRetrato = editing ? retratoCampos(ausViewCampos) : null;
|
|
}
|
|
|
|
async function openAusenciaViewModal(ramal) {
|
|
ausViewError.textContent = "";
|
|
try {
|
|
viewingAusencia = await pidFetchRamalAusencia(ramal.usuario_ausencia_ativa_id);
|
|
} catch (e) {
|
|
await pidAlert(e.message, { titulo: "Não foi possível abrir a ausência" });
|
|
return;
|
|
}
|
|
viewingRamalContext = ramal;
|
|
fillAusenciaViewFields(viewingAusencia, ramal);
|
|
if (ausViewEditBtn) ausViewEditBtn.hidden = !ramaisCanManage;
|
|
if (ausViewDeleteBtn) ausViewDeleteBtn.hidden = !ramaisCanManage;
|
|
setAusenciaViewMode("view");
|
|
ausViewModal.hidden = false;
|
|
}
|
|
|
|
function closeAusenciaViewModal() {
|
|
ausViewModal.hidden = true;
|
|
viewingAusencia = null;
|
|
viewingRamalContext = null;
|
|
ausViewRetrato = null;
|
|
}
|
|
|
|
async function tentarFecharAusenciaViewModal() {
|
|
if (await podeDescartar(ausViewRetrato, ausViewCampos)) closeAusenciaViewModal();
|
|
}
|
|
|
|
function openChamadoModal() {
|
|
chamadoIframe.src = PID_RAMAL_CHAMADO_URL;
|
|
chamadoModal.hidden = false;
|
|
}
|
|
|
|
function closeChamadoModal() {
|
|
chamadoModal.hidden = true;
|
|
chamadoIframe.src = "about:blank";
|
|
}
|
|
|
|
if (searchNomeInput) searchNomeInput.addEventListener("input", render);
|
|
if (searchDepartamentoInput) searchDepartamentoInput.addEventListener("input", render);
|
|
|
|
if (telSearchNomeInput) telSearchNomeInput.addEventListener("input", renderTelefonesExternos);
|
|
if (telSearchObsInput) telSearchObsInput.addEventListener("input", renderTelefonesExternos);
|
|
|
|
if (telAddBtn) telAddBtn.addEventListener("click", () => openTelefoneModal(null));
|
|
if (telFormCancelBtn) telFormCancelBtn.addEventListener("click", tentarFecharTelefoneModal);
|
|
if (telFormModal) {
|
|
telFormModal.addEventListener("click", (event) => {
|
|
if (event.target === telFormModal) tentarFecharTelefoneModal();
|
|
});
|
|
}
|
|
|
|
if (telFormSaveBtn) {
|
|
telFormSaveBtn.addEventListener("click", async () => {
|
|
const nome = telFormNome.value.trim();
|
|
if (!nome) {
|
|
telFormError.textContent = "Informe o nome.";
|
|
telFormNome.focus();
|
|
return;
|
|
}
|
|
telFormError.textContent = "";
|
|
|
|
const payload = {
|
|
nome,
|
|
ramal: telFormRamal.value.trim(),
|
|
telefone: telFormTelefone.value.trim(),
|
|
observacoes: telFormObservacoes.value.trim(),
|
|
};
|
|
|
|
const ocupado = editingTelefoneId ? "Salvando…" : "Adicionando…";
|
|
try {
|
|
await comTrava(telFormSaveBtn, ocupado, async () => {
|
|
if (editingTelefoneId) {
|
|
await pidUpdateTelefoneExterno(editingTelefoneId, payload);
|
|
} else {
|
|
await pidCreateTelefoneExterno(payload);
|
|
}
|
|
});
|
|
closeTelefoneModal();
|
|
await refreshTelefonesExternos();
|
|
} catch (e) {
|
|
telFormError.textContent = e.message;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (telTableBody) {
|
|
telTableBody.addEventListener("click", async (event) => {
|
|
const editBtn = event.target.closest("[data-tel-edit]");
|
|
if (editBtn) {
|
|
const id = Number(editBtn.getAttribute("data-tel-edit"));
|
|
const tel = telefonesExternos.find((t) => t.id === id);
|
|
if (tel) openTelefoneModal(tel);
|
|
return;
|
|
}
|
|
|
|
const deleteBtn = event.target.closest("[data-tel-delete]");
|
|
if (deleteBtn) {
|
|
const id = Number(deleteBtn.getAttribute("data-tel-delete"));
|
|
const tel = telefonesExternos.find((t) => t.id === id);
|
|
if (tel && (await pidConfirm(`Remover o telefone de "${tel.nome}"?`, { perigoso: true }))) {
|
|
try {
|
|
await comTrava(deleteBtn, null, () => pidDeleteTelefoneExterno(id));
|
|
} catch (e) {
|
|
await pidAlert(e.message, { titulo: "Não foi possível remover o telefone" });
|
|
return;
|
|
}
|
|
await refreshTelefonesExternos();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (funAddBtn) funAddBtn.addEventListener("click", () => openFuncaoModal(null));
|
|
if (funFormCancelBtn) funFormCancelBtn.addEventListener("click", tentarFecharFuncaoModal);
|
|
if (funFormModal) {
|
|
funFormModal.addEventListener("click", (event) => {
|
|
if (event.target === funFormModal) tentarFecharFuncaoModal();
|
|
});
|
|
}
|
|
|
|
if (funFormSaveBtn) {
|
|
funFormSaveBtn.addEventListener("click", async () => {
|
|
const comando = funFormComando.value.trim();
|
|
if (!comando) {
|
|
funFormError.textContent = "Informe o comando.";
|
|
funFormComando.focus();
|
|
return;
|
|
}
|
|
funFormError.textContent = "";
|
|
|
|
const payload = {
|
|
comando,
|
|
funcao: funFormFuncao.value.trim(),
|
|
resumo: funFormResumo.value.trim(),
|
|
};
|
|
|
|
const ocupado = editingFuncaoId ? "Salvando…" : "Adicionando…";
|
|
try {
|
|
await comTrava(funFormSaveBtn, ocupado, async () => {
|
|
if (editingFuncaoId) {
|
|
await pidUpdateFuncaoTelefonia(editingFuncaoId, payload);
|
|
} else {
|
|
await pidCreateFuncaoTelefonia(payload);
|
|
}
|
|
});
|
|
closeFuncaoModal();
|
|
await refreshFuncoesTelefonia();
|
|
} catch (e) {
|
|
funFormError.textContent = e.message;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (funTableBody) {
|
|
funTableBody.addEventListener("click", async (event) => {
|
|
const editBtn = event.target.closest("[data-fun-edit]");
|
|
if (editBtn) {
|
|
const id = Number(editBtn.getAttribute("data-fun-edit"));
|
|
const fun = funcoesTelefonia.find((f) => f.id === id);
|
|
if (fun) openFuncaoModal(fun);
|
|
return;
|
|
}
|
|
|
|
const deleteBtn = event.target.closest("[data-fun-delete]");
|
|
if (deleteBtn) {
|
|
const id = Number(deleteBtn.getAttribute("data-fun-delete"));
|
|
const fun = funcoesTelefonia.find((f) => f.id === id);
|
|
if (fun && (await pidConfirm(`Remover a função "${fun.comando}"?`, { perigoso: true }))) {
|
|
try {
|
|
await comTrava(deleteBtn, null, () => pidDeleteFuncaoTelefonia(id));
|
|
} catch (e) {
|
|
await pidAlert(e.message, { titulo: "Não foi possível remover a função" });
|
|
return;
|
|
}
|
|
await refreshFuncoesTelefonia();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (addBtn) addBtn.addEventListener("click", () => openFormModal(null));
|
|
if (formCancelBtn) formCancelBtn.addEventListener("click", tentarFecharFormModal);
|
|
if (formModal) {
|
|
formModal.addEventListener("click", (event) => {
|
|
if (event.target === formModal) tentarFecharFormModal();
|
|
});
|
|
}
|
|
|
|
if (formSaveBtn) {
|
|
formSaveBtn.addEventListener("click", async () => {
|
|
const nome = formNome.value.trim();
|
|
const departamento = formDepartamento.value.trim();
|
|
const numero = formNumero.value.trim();
|
|
|
|
if (editingMode !== "usuario" && !nome) {
|
|
formError.textContent = "Informe o nome.";
|
|
formNome.focus();
|
|
return;
|
|
}
|
|
formError.textContent = "";
|
|
|
|
const ocupado = editingMode === "create" ? "Adicionando…" : "Salvando…";
|
|
try {
|
|
await comTrava(formSaveBtn, ocupado, async () => {
|
|
if (editingMode === "usuario") {
|
|
await pidUpdateRamalUsuario(editingId, numero);
|
|
} else if (editingMode === "avulso") {
|
|
await pidUpdateRamal(editingId, { nome, departamento, numero });
|
|
} else {
|
|
await pidCreateRamal({ nome, departamento, numero });
|
|
}
|
|
});
|
|
closeFormModal();
|
|
await refresh();
|
|
} catch (e) {
|
|
formError.textContent = e.message;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (ausenciaBtn) ausenciaBtn.addEventListener("click", openAusenciaModal);
|
|
if (ausCancelBtn) ausCancelBtn.addEventListener("click", tentarFecharAusenciaModal);
|
|
if (ausenciaModal) {
|
|
ausenciaModal.addEventListener("click", (event) => {
|
|
if (event.target === ausenciaModal) tentarFecharAusenciaModal();
|
|
});
|
|
}
|
|
|
|
if (ausSaveBtn) {
|
|
ausSaveBtn.addEventListener("click", async () => {
|
|
const usuarioId = ausUsuario.value ? Number(ausUsuario.value) : null;
|
|
const dataInicio = ausDataInicio.value;
|
|
const dataFim = ausDataFim.value;
|
|
|
|
// Mensagem e cursor no primeiro campo que falta.
|
|
if (!usuarioId) {
|
|
ausError.textContent = "Selecione o usuário.";
|
|
ausUsuario.focus();
|
|
return;
|
|
}
|
|
if (!dataInicio) {
|
|
ausError.textContent = "Informe a data inicial.";
|
|
ausDataInicio.focus();
|
|
return;
|
|
}
|
|
if (!dataFim) {
|
|
ausError.textContent = "Informe a data final.";
|
|
ausDataFim.focus();
|
|
return;
|
|
}
|
|
ausError.textContent = "";
|
|
|
|
const payload = {
|
|
usuario: usuarioId,
|
|
data_inicio: dataInicio,
|
|
hora_inicio: ausHoraInicio.value || null,
|
|
data_fim: dataFim,
|
|
hora_volta: ausHoraVolta.value || null,
|
|
tipo: ausTipo.value.trim() || "Ausência",
|
|
observacoes: ausObservacoes.value.trim(),
|
|
};
|
|
|
|
try {
|
|
await comTrava(ausSaveBtn, "Salvando…", () => pidCreateRamalAusencia(payload));
|
|
closeAusenciaModal();
|
|
await refresh();
|
|
} catch (e) {
|
|
ausError.textContent = e.message;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (ausViewCloseBtn) ausViewCloseBtn.addEventListener("click", tentarFecharAusenciaViewModal);
|
|
if (ausViewModal) {
|
|
ausViewModal.addEventListener("click", (event) => {
|
|
if (event.target === ausViewModal) tentarFecharAusenciaViewModal();
|
|
});
|
|
}
|
|
|
|
if (ausViewEditBtn) ausViewEditBtn.addEventListener("click", () => setAusenciaViewMode("edit"));
|
|
|
|
if (ausViewCancelEditBtn) {
|
|
ausViewCancelEditBtn.addEventListener("click", async () => {
|
|
if (!(await podeDescartar(ausViewRetrato, ausViewCampos))) return;
|
|
fillAusenciaViewFields(viewingAusencia, viewingRamalContext);
|
|
setAusenciaViewMode("view");
|
|
});
|
|
}
|
|
|
|
if (ausViewSaveBtn) {
|
|
ausViewSaveBtn.addEventListener("click", async () => {
|
|
const dataInicio = ausViewDataInicio.value;
|
|
const dataFim = ausViewDataFim.value;
|
|
if (!dataInicio || !dataFim) {
|
|
ausViewError.textContent = "Informe as datas inicial e final.";
|
|
(dataInicio ? ausViewDataFim : ausViewDataInicio).focus();
|
|
return;
|
|
}
|
|
ausViewError.textContent = "";
|
|
|
|
const payload = {
|
|
data_inicio: dataInicio,
|
|
hora_inicio: ausViewHoraInicio.value || null,
|
|
data_fim: dataFim,
|
|
hora_volta: ausViewHoraVolta.value || null,
|
|
tipo: ausViewTipo.value.trim() || "Ausência",
|
|
observacoes: ausViewObservacoes.value.trim(),
|
|
};
|
|
|
|
try {
|
|
await comTrava(ausViewSaveBtn, "Salvando…", () => pidUpdateRamalAusencia(viewingAusencia.id, payload));
|
|
closeAusenciaViewModal();
|
|
await refresh();
|
|
} catch (e) {
|
|
ausViewError.textContent = e.message;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (ausViewDeleteBtn) {
|
|
ausViewDeleteBtn.addEventListener("click", async () => {
|
|
if (!(await pidConfirm("Deletar esta ausência?", { perigoso: true }))) return;
|
|
try {
|
|
await comTrava(ausViewDeleteBtn, null, () => pidDeleteRamalAusencia(viewingAusencia.id));
|
|
closeAusenciaViewModal();
|
|
await refresh();
|
|
} catch (e) {
|
|
await pidAlert(e.message, { titulo: "Não foi possível deletar a ausência" });
|
|
}
|
|
});
|
|
}
|
|
|
|
if (chamadoBtn) chamadoBtn.addEventListener("click", openChamadoModal);
|
|
if (chamadoCloseBtn) chamadoCloseBtn.addEventListener("click", closeChamadoModal);
|
|
if (chamadoModal) {
|
|
chamadoModal.addEventListener("click", (event) => {
|
|
if (event.target === chamadoModal) closeChamadoModal();
|
|
});
|
|
}
|
|
|
|
tableBody.addEventListener("click", async (event) => {
|
|
const editBtn = event.target.closest("[data-ram-edit]");
|
|
if (editBtn) {
|
|
const id = editBtn.getAttribute("data-ram-edit");
|
|
const ramal = ramais.find((r) => r.id === id);
|
|
if (ramal) openFormModal(ramal);
|
|
return;
|
|
}
|
|
|
|
const deleteBtn = event.target.closest("[data-ram-delete]");
|
|
if (deleteBtn) {
|
|
const avulsoId = Number(deleteBtn.getAttribute("data-ram-delete"));
|
|
const ramal = ramais.find((r) => r.avulso_id === avulsoId);
|
|
if (ramal && (await pidConfirm(`Remover o ramal de "${ramal.nome_exibicao}"?`, { perigoso: true }))) {
|
|
try {
|
|
await comTrava(deleteBtn, null, () => pidDeleteRamal(avulsoId));
|
|
} catch (e) {
|
|
await pidAlert(e.message, { titulo: "Não foi possível remover o ramal" });
|
|
return;
|
|
}
|
|
await refresh();
|
|
}
|
|
return;
|
|
}
|
|
|
|
const verAusenciaBtn = event.target.closest("[data-ram-ver-ausencia]");
|
|
if (verAusenciaBtn) {
|
|
const id = Number(verAusenciaBtn.getAttribute("data-ram-ver-ausencia"));
|
|
const ramal = ramais.find((r) => r.usuario_ausencia_ativa_id === id);
|
|
if (ramal) await openAusenciaViewModal(ramal);
|
|
}
|
|
});
|
|
|
|
renderRamTabs();
|
|
await Promise.all([refresh(), refreshTelefonesExternos(), refreshFuncoesTelefonia()]);
|
|
});
|