File manager - Edit - /var/www/html/portal/public/pmar/js/indicadoresTi.js
Back
(function () { 'use strict'; const csrfToken = () => document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? ''; const qs = (sel, ctx = document) => ctx.querySelector(sel); const qsa = (sel, ctx = document) => [...ctx.querySelectorAll(sel)]; // ============ FORMULÁRIOS COLAPSÁVEIS ============ const WRAPPER_TRANSITION = 'max-height 0.4s ease, opacity 0.3s ease'; function medirAltura(wrapper) { const prevMaxHeight = wrapper.style.maxHeight; wrapper.style.transition = 'none'; wrapper.style.maxHeight = '9999px'; const h = wrapper.scrollHeight; wrapper.style.maxHeight = prevMaxHeight; void wrapper.offsetHeight; wrapper.style.transition = WRAPPER_TRANSITION; return h; } function toggleWrapper(wrapper, resumo, expandir, imediato = false) { wrapper.dataset.aberto = expandir ? 'true' : 'false'; if (expandir) { if (imediato) { wrapper.style.transition = 'none'; wrapper.style.overflow = 'hidden'; wrapper.style.maxHeight = 'none'; wrapper.style.opacity = '1'; setTimeout(() => { wrapper.style.transition = WRAPPER_TRANSITION; }, 50); } else { wrapper.style.overflow = 'hidden'; const h = medirAltura(wrapper); wrapper.style.maxHeight = h + 'px'; wrapper.style.opacity = '1'; wrapper.addEventListener('transitionend', function onExpanded(e) { if (e.propertyName !== 'max-height') return; wrapper.removeEventListener('transitionend', onExpanded); if (wrapper.dataset.aberto === 'true') { wrapper.style.maxHeight = 'none'; } }); } } else { if (imediato) { wrapper.style.transition = 'none'; wrapper.style.overflow = 'hidden'; wrapper.style.maxHeight = '0'; wrapper.style.opacity = '0'; setTimeout(() => { wrapper.style.transition = WRAPPER_TRANSITION; }, 50); } else { if (!wrapper.style.maxHeight || wrapper.style.maxHeight === 'none') { wrapper.style.transition = 'none'; wrapper.style.maxHeight = wrapper.scrollHeight + 'px'; void wrapper.offsetHeight; } wrapper.style.overflow = 'hidden'; wrapper.style.transition = WRAPPER_TRANSITION; wrapper.style.maxHeight = '0'; wrapper.style.opacity = '0'; } } const chevron = qs('.chamado-resumo-chevron', resumo); const hint = qs('.chamado-resumo-hint', resumo); if (chevron) chevron.style.transform = expandir ? 'rotate(180deg)' : ''; if (hint) hint.textContent = expandir ? 'Clique para recolher' : 'Clique para expandir'; } let formsColapsaveisIniciados = false; function initFormsColapsaveis() { if (formsColapsaveisIniciados) return; formsColapsaveisIniciados = true; qsa('.tab-pane').forEach((pane) => { const resumo = qs('.chamado-resumo-header', pane); const wrapper = qs('.chamado-form-wrapper', pane); if (!resumo || !wrapper) return; const expandir = pane.classList.contains('show') && pane.id !== 'painel-novo-chamado'; toggleWrapper(wrapper, resumo, expandir, true); resumo.addEventListener('click', () => toggleWrapper(wrapper, resumo, wrapper.dataset.aberto !== 'true')); const tabBtn = document.getElementById(pane.getAttribute('aria-labelledby')); tabBtn?.addEventListener('shown.bs.tab', () => { if (wrapper.dataset.aberto !== 'true') toggleWrapper(wrapper, resumo, true); }); }); document.addEventListener('hide.bs.tab', (e) => { const pane = qs(e.target.getAttribute('data-bs-target')); const wrapper = pane && qs('.chamado-form-wrapper', pane); const resumo = pane && qs('.chamado-resumo-header', pane); if (wrapper && resumo) toggleWrapper(wrapper, resumo, false); }); } // ============ CASCATA SETOR/ASSUNTO (para formulários de chamado) ============ function preencherSelect(select, itens) { if (!select) return; select.innerHTML = '<option value="">Selecione</option>'; itens.forEach(({ id, nome }) => select.add(new Option(nome, id))); } async function carregarOpcoes(url, select) { preencherSelect(select, []); select?.setAttribute('disabled', 'true'); try { const res = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (res.ok) preencherSelect(select, await res.json()); } catch (e) { console.error(e); } finally { select?.removeAttribute('disabled'); } } function initCascata() { document.addEventListener('change', async (e) => { const form = e.target.closest('form.chamado-form'); if (!form) return; if (e.target.matches('.js-setor-select')) { await carregarOpcoes(`/admin/indicadoresTi/assuntos/${e.target.value}`, qs('.js-assunto-select', form)); preencherSelect(qs('.js-subassunto-select', form), []); } else if (e.target.matches('.js-assunto-select')) { await carregarOpcoes(`/admin/indicadoresTi/subassuntos/${e.target.value}`, qs('.js-subassunto-select', form)); } }); } // ============ CASCATA RELATÓRIO (página de filtros) ============ function initCascataRelatorio() { const setorSel = document.getElementById('setor_id'); const assuntoSel = document.getElementById('assunto_id'); const subassuntoSel = document.getElementById('subassunto_id'); if (!setorSel || !assuntoSel || !subassuntoSel) return; setorSel.addEventListener('change', async () => { const id = setorSel.value; assuntoSel.innerHTML = '<option value="">Todos os assuntos</option>'; subassuntoSel.innerHTML = '<option value="">Todos os subassuntos</option>'; if (id) { await carregarOpcoes(`/admin/indicadoresTi/relatorio/assuntos/${id}`, assuntoSel); assuntoSel.disabled = false; } else { assuntoSel.disabled = true; subassuntoSel.disabled = true; } }); assuntoSel.addEventListener('change', async () => { const id = assuntoSel.value; subassuntoSel.innerHTML = '<option value="">Todos os subassuntos</option>'; if (id) { await carregarOpcoes(`/admin/indicadoresTi/relatorio/subassuntos/${id}`, subassuntoSel); subassuntoSel.disabled = false; } else { subassuntoSel.disabled = true; } }); if (!setorSel.value) { assuntoSel.disabled = true; subassuntoSel.disabled = true; } else if (!assuntoSel.value) { subassuntoSel.disabled = true; } } // ============ AUTOCOMPLETE CLIENTE ============ let clienteTimeout, clienteDropdown, clienteInputAtivo = null; function criarDropdownCliente() { if (clienteDropdown) return clienteDropdown; const div = document.createElement('div'); div.className = 'list-group shadow-sm js-cliente-suggestions'; div.style.cssText = 'position:absolute;z-index:9999;max-height:250px;overflow-y:auto;display:none;background:#fff;border:1px solid #dee2e6;border-top:none;'; document.body.appendChild(div); return clienteDropdown = div; } function posicionarDropdown(input, dropdown) { const rect = input.getBoundingClientRect(); dropdown.style.top = (rect.bottom + window.scrollY) + 'px'; dropdown.style.left = (rect.left + window.scrollX) + 'px'; dropdown.style.width = rect.width + 'px'; } function obterElementosCliente(form) { return { hiddenId: form.querySelector('.js-solicitante-id'), hiddenTipo: form.querySelector('.js-solicitante-tipo'), limparBtn: form.querySelector('.js-limpar-cliente'), info: form.querySelector('.js-cliente-info'), detalhes: form.querySelector('.js-cliente-detalhes'), }; } function selecionarCliente(input, option) { const form = input.closest('form'); const { hiddenId, hiddenTipo, limparBtn, info, detalhes } = obterElementosCliente(form); const { nome, cpf, setor, telefone, tipo } = option.dataset; input.value = tipo === 'usuario' ? (cpf ? `${nome} — ${cpf}` : nome) : nome; hiddenId.value = option.dataset.id; hiddenTipo.value = tipo; if (info && detalhes) { detalhes.textContent = [ tipo === 'usuario' ? 'Usuário interno' : 'Cliente', setor && `Setor: ${setor}`, telefone && `Tel: ${telefone}` ].filter(Boolean).join(' | '); info.style.display = 'block'; } if (limparBtn) limparBtn.style.display = 'inline-block'; } function limparCliente(form) { const input = form.querySelector('.js-cliente-search'); const { hiddenId, hiddenTipo, limparBtn, info } = obterElementosCliente(form); if (input) input.value = ''; if (hiddenId) hiddenId.value = ''; if (hiddenTipo) hiddenTipo.value = ''; if (limparBtn) limparBtn.style.display = 'none'; if (info) info.style.display = 'none'; } function initClienteAutocomplete() { const dropdown = criarDropdownCliente(); document.addEventListener('focusin', (e) => { if (e.target.matches('.js-cliente-search')) { clienteInputAtivo = e.target; } }); document.addEventListener('input', (e) => { if (!e.target.matches('.js-cliente-search')) return; const input = e.target; clienteInputAtivo = input; const form = input.closest('form'); const termo = input.value.trim(); clearTimeout(clienteTimeout); if (!termo) { limparCliente(form); dropdown.style.display = 'none'; return; } clienteTimeout = setTimeout(async () => { if (termo.length < 2) { dropdown.style.display = 'none'; return; } try { const res = await fetch(`/admin/indicadoresTi/clientes/buscar?q=${encodeURIComponent(termo)}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (!res.ok) throw new Error('Erro'); const clientes = await res.json(); dropdown.innerHTML = clientes.length ? clientes.map(c => { const identificador = c.cpf || c.matricula || ''; const labelId = c.cpf ? c.cpf : (c.matricula ? `Mat. ${c.matricula}` : ''); return ` <button type="button" class="list-group-item list-group-item-action js-cliente-option" data-id="${c.id}" data-nome="${c.nome}" data-cpf="${identificador}" data-setor="${c.setor || ''}" data-telefone="${c.telefone || ''}" data-tipo="${c.tipo}"> <strong>${c.nome}</strong>${labelId ? ` — ${labelId}` : ''} <br> <small class="text-muted"> ${c.tipo === 'usuario' ? 'Usuário' : 'Cliente'} ${c.setor ? ` • ${c.setor}` : ''} </small> </button>`; }).join('') : '<div class="list-group-item text-muted">Nenhum resultado</div>'; posicionarDropdown(input, dropdown); dropdown.style.display = 'block'; } catch { dropdown.style.display = 'none'; } }, 300); }); document.addEventListener('click', (e) => { const option = e.target.closest('.js-cliente-option'); if (option) { if (clienteInputAtivo) { selecionarCliente(clienteInputAtivo, option); dropdown.style.display = 'none'; clienteInputAtivo.focus(); } return; } if (e.target.closest('.js-limpar-cliente')) { limparCliente(e.target.closest('.js-limpar-cliente').closest('form')); dropdown.style.display = 'none'; return; } if (!e.target.closest('.js-cliente-search') && !e.target.closest('.js-cliente-suggestions')) { dropdown.style.display = 'none'; } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') dropdown.style.display = 'none'; }); const reposicionar = () => { if (clienteInputAtivo && dropdown.style.display === 'block') posicionarDropdown(clienteInputAtivo, dropdown); }; window.addEventListener('resize', reposicionar); window.addEventListener('scroll', reposicionar, true); } // ============ NOVO CLIENTE MODAL ============ function fecharModalCompletamente(modalEl) { const btnFechar = modalEl.querySelector('[data-bs-dismiss="modal"]'); if (btnFechar) { btnFechar.click(); } else { bootstrap.Modal.getOrCreateInstance(modalEl).hide(); } const limpar = () => { document.querySelectorAll('.modal-backdrop').forEach(el => el.remove()); document.body.classList.remove('modal-open'); document.body.style.removeProperty('overflow'); document.body.style.removeProperty('padding-right'); }; modalEl.addEventListener('hidden.bs.modal', limpar, { once: true }); setTimeout(limpar, 400); } function initNovoCliente() { const erroBox = document.getElementById('novo-cliente-erro'); const mostrarErro = (msg) => { if (erroBox) { erroBox.textContent = msg; erroBox.classList.remove('d-none'); } }; const esconderErro = () => { if (erroBox) { erroBox.textContent = ''; erroBox.classList.add('d-none'); } }; document.getElementById('nc-tipo-identificacao')?.addEventListener('change', function () { const isCpf = this.value === 'cpf'; document.getElementById('nc-grupo-cpf')?.classList.toggle('d-none', !isCpf); document.getElementById('nc-grupo-matricula')?.classList.toggle('d-none', isCpf); }); document.getElementById('modal-novo-cliente')?.addEventListener('hidden.bs.modal', () => { esconderErro(); ['nc-nome', 'nc-cpf', 'nc-matricula', 'nc-setor', 'nc-telefone'].forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; }); const sel = document.getElementById('nc-tipo-identificacao'); if (sel) { sel.value = 'cpf'; sel.dispatchEvent(new Event('change')); } }); document.addEventListener('click', async (e) => { if (!e.target.matches('#btn-salvar-cliente')) return; const btn = e.target; const spinner = document.getElementById('spinner-salvar-cliente'); const modalEl = document.getElementById('modal-novo-cliente'); const tipo = document.getElementById('nc-tipo-identificacao')?.value ?? 'cpf'; const dados = { nome: document.getElementById('nc-nome')?.value.trim() || null, cpf: tipo === 'cpf' ? (document.getElementById('nc-cpf')?.value.trim() || null) : null, matricula: tipo === 'matricula' ? (document.getElementById('nc-matricula')?.value.trim() || null) : null, setor: document.getElementById('nc-setor')?.value.trim() || null, telefone: document.getElementById('nc-telefone')?.value.trim() || null, }; esconderErro(); if (!dados.nome) { mostrarErro('O nome é obrigatório.'); return; } if (!dados.cpf && !dados.matricula) { mostrarErro(tipo === 'cpf' ? 'Informe o CPF.' : 'Informe a Matrícula.'); return; } btn.disabled = true; spinner?.classList.remove('d-none'); try { const res = await fetch('/admin/indicadoresTi/clientes', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify(dados), }); const data = await res.json(); if (!res.ok || !data.success) throw new Error(data.message ?? 'Erro ao salvar cliente.'); fecharModalCompletamente(modalEl); const identificador = data.cpf || data.matricula || null; qsa('.js-cliente-search').forEach(input => { if (!input.value) { try { selecionarCliente(input, { dataset: { ...data, cpf: identificador, tipo: 'cliente' } }); } catch (_) { } } }); } catch (err) { mostrarErro(err.message); } finally { btn.disabled = false; spinner?.classList.add('d-none'); } }); } // ============ TEMPO EM ANDAMENTO ============ function atualizarTemposEmAndamento() { qsa('.tempo-em-andamento').forEach(el => { if (!el.dataset.inicio) return; const acumulado = parseInt(el.dataset.acumulado || '0', 10); const current = Math.max(0, Math.floor((Date.now() - new Date(el.dataset.inicio)) / 1000)); const total = acumulado + current; const d = Math.floor(total / 86400), h = Math.floor((total % 86400) / 3600), m = Math.floor((total % 3600) / 60), s = total % 60; el.textContent = [d && `${d}d`, h && `${h}h`, m && `${m}min`, `${s}s`].filter(Boolean).join(' '); }); } // ============ UTILITÁRIO ============ function escHtml(str) { return String(str ?? '') .replace(/&/g, '&').replace(/</g, '<') .replace(/>/g, '>').replace(/"/g, '"'); } // ============ BUSCA DE UNIDADE ORGANIZACIONAL ============ let unidadeTimeout, unidadeDropdown = null, unidadeInputAtivo = null; function criarDropdownUnidade() { if (unidadeDropdown) return unidadeDropdown; const div = document.createElement('div'); div.className = 'list-group shadow-sm js-unidade-suggestions'; div.style.cssText = 'position:absolute;z-index:9999;max-height:250px;overflow-y:auto;display:none;background:#fff;border:1px solid #dee2e6;border-top:none;'; document.body.appendChild(div); return (unidadeDropdown = div); } function initUnidadeSearch() { const dropdown = criarDropdownUnidade(); document.addEventListener('focusin', (e) => { if (e.target.matches('.js-unidade-search')) unidadeInputAtivo = e.target; }); document.addEventListener('input', (e) => { if (!e.target.matches('.js-unidade-search')) return; const input = e.target; unidadeInputAtivo = input; const wrapper = input.closest('.position-relative'); const hiddenId = wrapper?.querySelector('.js-unidade-id'); const url = input.dataset.url; const termo = input.value.trim(); clearTimeout(unidadeTimeout); if (hiddenId) hiddenId.value = ''; if (termo.length < 2) { dropdown.style.display = 'none'; return; } unidadeTimeout = setTimeout(async () => { try { const res = await fetch(`${url}?q=${encodeURIComponent(termo)}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); const dados = await res.json(); dropdown.innerHTML = dados.length ? dados.map(u => ` <button type="button" class="list-group-item list-group-item-action js-unidade-option" data-id="${escHtml(u.id)}" data-texto="${escHtml(u.texto)}"> <strong>${escHtml(u.texto)}</strong> ${u.secretaria ? `<small class="text-muted ms-1">(${escHtml(u.secretaria)})</small>` : ''} </button>`).join('') : '<div class="list-group-item text-muted small">Nenhuma unidade encontrada.</div>'; posicionarDropdown(input, dropdown); dropdown.style.display = 'block'; } catch { dropdown.style.display = 'none'; } }, 300); }); document.addEventListener('click', (e) => { const option = e.target.closest('.js-unidade-option'); if (option && unidadeInputAtivo) { const wrapper = unidadeInputAtivo.closest('.position-relative'); const hiddenId = wrapper?.querySelector('.js-unidade-id'); unidadeInputAtivo.value = option.dataset.texto; if (hiddenId) hiddenId.value = option.dataset.id; dropdown.style.display = 'none'; return; } if (!e.target.closest('.js-unidade-search') && !e.target.closest('.js-unidade-suggestions')) { dropdown.style.display = 'none'; } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') dropdown.style.display = 'none'; }); const reposicionar = () => { if (unidadeInputAtivo && dropdown.style.display === 'block') posicionarDropdown(unidadeInputAtivo, dropdown); }; window.addEventListener('resize', reposicionar); window.addEventListener('scroll', reposicionar, true); } // ============ BUSCA DE PARTICIPANTE ============ let participanteTimeout, participanteDropdown, participanteInputAtivo = null; function criarDropdownParticipante() { if (participanteDropdown) return participanteDropdown; const div = document.createElement('div'); div.className = 'list-group shadow-sm js-participante-suggestions'; div.style.cssText = 'position:fixed;z-index:9999;max-height:250px;overflow-y:auto;display:none;background:#fff;border:1px solid #dee2e6;border-top:none;'; document.body.appendChild(div); return (participanteDropdown = div); } function posicionarDropdownFixed(input, dropdown) { const rect = input.getBoundingClientRect(); dropdown.style.top = rect.bottom + 'px'; dropdown.style.left = rect.left + 'px'; dropdown.style.width = rect.width + 'px'; } function initParticipanteSearch() { const dropdown = criarDropdownParticipante(); document.addEventListener('focusin', (e) => { if (e.target.matches('.js-participante-search')) participanteInputAtivo = e.target; }); document.addEventListener('input', (e) => { if (!e.target.matches('.js-participante-search')) return; const input = e.target; participanteInputAtivo = input; const termo = input.value.trim(); clearTimeout(participanteTimeout); if (!termo) { dropdown.style.display = 'none'; return; } participanteTimeout = setTimeout(async () => { if (termo.length < 2) { dropdown.style.display = 'none'; return; } try { const res = await fetch(`/admin/indicadoresTi/clientes/buscar?q=${encodeURIComponent(termo)}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const dados = await res.json(); const usuarios = dados.filter(u => u.tipo === 'usuario'); dropdown.innerHTML = usuarios.length ? usuarios.map(u => ` <button type="button" class="list-group-item list-group-item-action js-participante-add-btn d-flex justify-content-between align-items-center" data-id="${u.id}" data-chamado="${input.dataset.chamado}"> <span> <strong>${u.nome}</strong>${u.cpf ? ` — ${u.cpf}` : ''} <br><small class="text-muted">${u.setor ? `Usuário • ${u.setor}` : 'Usuário'}</small> </span> <span class="btn btn-sm btn-outline-primary ms-2 flex-shrink-0"> <i class="fa-solid fa-plus me-1"></i>Adicionar </span> </button> `).join('') : '<div class="list-group-item text-muted small">Nenhum usuário encontrado.</div>'; posicionarDropdownFixed(input, dropdown); dropdown.style.display = 'block'; } catch (err) { console.error('[participante]', err); dropdown.style.display = 'none'; } }, 300); }); document.addEventListener('click', (e) => { const addBtn = e.target.closest('.js-participante-add-btn'); if (addBtn) { const form = document.getElementById('form-add-participante-' + addBtn.dataset.chamado); if (form) { form.querySelector('.js-participante-user-id').value = addBtn.dataset.id; form.submit(); } dropdown.style.display = 'none'; return; } if (!e.target.closest('.js-participante-search') && !e.target.closest('.js-participante-suggestions')) { dropdown.style.display = 'none'; } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') dropdown.style.display = 'none'; }); const reposicionar = () => { if (participanteInputAtivo && dropdown.style.display === 'block') posicionarDropdownFixed(participanteInputAtivo, dropdown); }; window.addEventListener('resize', reposicionar); window.addEventListener('scroll', reposicionar, true); } // ============ COLAPSÁVEIS DE SETOR ============ function initSetorCollapses() { document.querySelectorAll('.js-setor-toggle').forEach(btn => { const collapseEl = document.querySelector(btn.dataset.bsTarget); if (!collapseEl) return; const chevron = btn.querySelector('.js-setor-chevron'); const instance = new bootstrap.Collapse(collapseEl, { toggle: false }); btn.addEventListener('click', () => { collapseEl.classList.contains('show') ? instance.hide() : instance.show(); }); collapseEl.addEventListener('show.bs.collapse', () => { if (chevron) chevron.style.transform = 'rotate(180deg)'; btn.setAttribute('aria-expanded', 'true'); }); collapseEl.addEventListener('hide.bs.collapse', () => { if (chevron) chevron.style.transform = ''; btn.setAttribute('aria-expanded', 'false'); }); }); } // ============ GERENCIAR CLIENTES ============ function initGerenciarClientes() { const modalEditarEl = document.getElementById('modal-editar-cliente'); const modalNovoEl = document.getElementById('gc-modal-novo-cliente'); if (!modalEditarEl && !modalNovoEl) return; let clienteEditandoId = null; const erroBoxNovo = document.getElementById('gc-nc-erros'); const erroListaNovo = document.getElementById('gc-nc-erros-lista'); const mostrarErroNovo = (msg) => { if (erroListaNovo) erroListaNovo.innerHTML = `<li>${escHtml(msg)}</li>`; erroBoxNovo?.classList.remove('d-none'); }; const esconderErroNovo = () => { if (erroListaNovo) erroListaNovo.innerHTML = ''; erroBoxNovo?.classList.add('d-none'); }; document.getElementById('gc-nc-tipo')?.addEventListener('change', function () { const isCpf = this.value === 'cpf'; document.getElementById('gc-nc-grupo-cpf')?.classList.toggle('d-none', !isCpf); document.getElementById('gc-nc-grupo-matricula')?.classList.toggle('d-none', isCpf); }); modalNovoEl?.addEventListener('hidden.bs.modal', () => { esconderErroNovo(); ['gc-nc-nome', 'gc-nc-cpf', 'gc-nc-matricula', 'gc-nc-setor', 'gc-nc-telefone'].forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; }); const sel = document.getElementById('gc-nc-tipo'); if (sel) { sel.value = 'cpf'; sel.dispatchEvent(new Event('change')); } }); document.addEventListener('click', async (e) => { if (!e.target.matches('#gc-btn-salvar-novo')) return; const btn = e.target; const spinner = document.getElementById('gc-spinner-novo'); const tipo = document.getElementById('gc-nc-tipo')?.value ?? 'cpf'; const dados = { nome: document.getElementById('gc-nc-nome')?.value.trim() || null, cpf: tipo === 'cpf' ? (document.getElementById('gc-nc-cpf')?.value.trim() || null) : null, matricula: tipo === 'matricula' ? (document.getElementById('gc-nc-matricula')?.value.trim() || null) : null, setor: document.getElementById('gc-nc-setor')?.value.trim() || null, telefone: document.getElementById('gc-nc-telefone')?.value.trim() || null, }; esconderErroNovo(); if (!dados.nome) { mostrarErroNovo('O nome é obrigatório.'); return; } if (!dados.cpf && !dados.matricula) { mostrarErroNovo(tipo === 'cpf' ? 'Informe o CPF.' : 'Informe a Matrícula.'); return; } btn.disabled = true; spinner?.classList.remove('d-none'); try { const res = await fetch('/admin/indicadoresTi/gerenciar/clientes', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify(dados), }); const data = await res.json(); if (!res.ok) throw new Error(data.message ?? 'Erro ao salvar.'); fecharModalCompletamente(modalNovoEl); location.reload(); } catch (err) { mostrarErroNovo(err.message); } finally { btn.disabled = false; spinner?.classList.add('d-none'); } }); const erroBoxEditar = document.getElementById('editar-cliente-erro'); const mostrarErroEditar = (msg) => { if (erroBoxEditar) { erroBoxEditar.textContent = msg; erroBoxEditar.classList.remove('d-none'); } }; const esconderErroEditar = () => { if (erroBoxEditar) { erroBoxEditar.textContent = ''; erroBoxEditar.classList.add('d-none'); } }; document.getElementById('ec-tipo-identificacao')?.addEventListener('change', function () { const isCpf = this.value === 'cpf'; document.getElementById('ec-grupo-cpf')?.classList.toggle('d-none', !isCpf); document.getElementById('ec-grupo-matricula')?.classList.toggle('d-none', isCpf); }); modalEditarEl?.addEventListener('show.bs.modal', async (e) => { const id = e.relatedTarget?.dataset.id; if (!id) return; clienteEditandoId = id; esconderErroEditar(); ['ec-nome', 'ec-cpf', 'ec-matricula', 'ec-setor', 'ec-telefone'].forEach(elId => { const el = document.getElementById(elId); if (el) el.value = ''; }); try { const res = await fetch(`/admin/indicadoresTi/gerenciar/clientes/${id}`, { headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, }); const json = await res.json(); ['nome', 'setor', 'telefone'].forEach(campo => { const el = document.getElementById(`ec-${campo}`); if (el) el.value = json[campo] ?? ''; }); const tipoSel = document.getElementById('ec-tipo-identificacao'); if (tipoSel) { tipoSel.value = json.cpf ? 'cpf' : 'matricula'; tipoSel.dispatchEvent(new Event('change')); } const cpfEl = document.getElementById('ec-cpf'); if (cpfEl) cpfEl.value = json.cpf ?? ''; const matEl = document.getElementById('ec-matricula'); if (matEl) matEl.value = json.matricula ?? ''; } catch { mostrarErroEditar('Erro ao carregar dados do cliente.'); } }); modalEditarEl?.addEventListener('hidden.bs.modal', () => { esconderErroEditar(); ['ec-nome', 'ec-cpf', 'ec-matricula', 'ec-setor', 'ec-telefone'].forEach(elId => { const el = document.getElementById(elId); if (el) el.value = ''; }); const sel = document.getElementById('ec-tipo-identificacao'); if (sel) { sel.value = 'cpf'; sel.dispatchEvent(new Event('change')); } }); document.addEventListener('click', async (e) => { if (!e.target.matches('#btn-salvar-editar-cliente')) return; const btn = e.target; const spinner = document.getElementById('spinner-salvar-editar-cliente'); if (!clienteEditandoId) return; const tipo = document.getElementById('ec-tipo-identificacao')?.value ?? 'cpf'; const dados = { nome: document.getElementById('ec-nome')?.value.trim() || null, cpf: tipo === 'cpf' ? (document.getElementById('ec-cpf')?.value.trim() || null) : null, matricula: tipo === 'matricula' ? (document.getElementById('ec-matricula')?.value.trim() || null) : null, setor: document.getElementById('ec-setor')?.value.trim() || null, telefone: document.getElementById('ec-telefone')?.value.trim() || null, }; esconderErroEditar(); if (!dados.nome) { mostrarErroEditar('O nome é obrigatório.'); return; } if (!dados.cpf && !dados.matricula) { mostrarErroEditar(tipo === 'cpf' ? 'Informe o CPF.' : 'Informe a Matrícula.'); return; } btn.disabled = true; spinner?.classList.remove('d-none'); try { const res = await fetch(`/admin/indicadoresTi/gerenciar/clientes/${clienteEditandoId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify(dados), }); const data = await res.json(); if (!res.ok) throw new Error(data.message ?? 'Erro ao salvar.'); fecharModalCompletamente(modalEditarEl); location.reload(); } catch (err) { mostrarErroEditar(err.message); } finally { btn.disabled = false; spinner?.classList.add('d-none'); } }); document.addEventListener('submit', (e) => { const form = e.target.closest('.gc-form-excluir'); if (!form) return; if (!confirm(`Excluir o cliente "${escHtml(form.dataset.nome ?? '')}"? Esta ação não pode ser desfeita.`)) { e.preventDefault(); } }); } // ============ GERENCIAR SETOR COLLAPSE ============ function initGerenciarCollapse() { document.querySelectorAll('.js-gerenciar-setor-toggle').forEach(function (btn) { btn.addEventListener('click', function () { var el = document.getElementById(btn.getAttribute('data-target-id')); var icon = btn.querySelector('i'); if (!el) return; var hidden = el.style.display === 'none'; el.style.display = hidden ? 'block' : 'none'; if (icon) icon.style.transform = hidden ? 'rotate(180deg)' : ''; }); }); } // ============ MODAL ESCOLHA DE GRÁFICOS ============ const CHARTS_PREF_KEY = 'indicadoresTi_charts_v1'; const CHART_DEFS = [ { key: 'status', label: 'Por Status', icon: 'fa-circle-half-stroke', color: 'text-primary', always: true }, { key: 'prioridade', label: 'Por Prioridade', icon: 'fa-exclamation-triangle', color: 'text-warning', always: true }, { key: 'setor', label: 'Por Setor', icon: 'fa-building', color: 'text-success', always: true }, { key: 'mes', label: 'Chamados por Mês', icon: 'fa-calendar', color: 'text-info', always: false, hint: 'Disponível quando o período selecionado ultrapassa 30 dias.' }, { key: 'semana', label: 'Chamados por Semana', icon: 'fa-calendar-week', color: 'text-secondary', always: false, hint: 'Disponível quando o período selecionado é de até 90 dias.' }, ]; function calcDisponivel(dias) { return CHART_DEFS.map(c => { if (c.always) return { ...c, disponivel: true }; if (c.key === 'mes') return { ...c, disponivel: dias === null || dias > 30 }; if (c.key === 'semana') return { ...c, disponivel: dias !== null && dias <= 90 }; return { ...c, disponivel: false }; }); } function calcPadrao(dias) { return calcDisponivel(dias).filter(c => c.disponivel).map(c => c.key); } function loadPrefs() { try { return JSON.parse(localStorage.getItem(CHARTS_PREF_KEY)) || null; } catch { return null; } } function savePrefs(keys) { localStorage.setItem(CHARTS_PREF_KEY, JSON.stringify(keys)); } function initRelatorioModal() { const btn = document.getElementById('btn-visualizar-previa'); const form = btn?.closest('form') || document.getElementById('form-filtros-relatorio'); const modalEl = document.getElementById('modal-escolha-graficos'); const bodyEl = document.getElementById('modal-graficos-body'); const confirmBtn = document.getElementById('btn-confirmar-graficos'); if (!btn || !modalEl) return; btn.addEventListener('click', () => { const inicio = btn.dataset.inicio || qs('#data_inicio')?.value || ''; const fim = btn.dataset.fim || qs('#data_fim')?.value || ''; const dias = (inicio && fim) ? Math.round((new Date(fim) - new Date(inicio)) / 86400000) : null; const lista = calcDisponivel(dias); const prefs = loadPrefs(); const padrao = prefs ? prefs.filter(k => lista.find(c => c.key === k && c.disponivel)) : calcPadrao(dias); bodyEl.innerHTML = ` <p class="text-muted small mb-3"> ${dias !== null ? `Período selecionado: <strong>${dias} dias</strong>.` : 'Nenhum período selecionado — exibindo todos os dados.'} Selecione os gráficos que deseja visualizar: </p> ${lista.map(c => ` <div class="form-check mb-2 ${c.disponivel ? '' : 'opacity-50'}"> <input class="form-check-input" type="checkbox" id="gc-${c.key}" value="${c.key}" ${padrao.includes(c.key) ? 'checked' : ''} ${c.disponivel ? '' : 'disabled'}> <label class="form-check-label" for="gc-${c.key}"> <i class="fa-solid ${c.icon} me-1 ${c.color}"></i> <strong>${c.label}</strong> ${!c.disponivel ? `<small class="text-muted ms-1">— ${c.hint}</small>` : ''} </label> </div> `).join('')} `; bootstrap.Modal.getOrCreateInstance(modalEl).show(); }); confirmBtn?.addEventListener('click', () => { const selecionados = [...modalEl.querySelectorAll('.form-check-input:checked')].map(el => el.value); savePrefs(selecionados); bootstrap.Modal.getOrCreateInstance(modalEl).hide(); form?.submit(); }); } // ============ GRÁFICOS DO RELATÓRIO ============ const _charts = {}; function initRelatorioCharts() { const container = document.getElementById('relatorio-charts'); if (!container) return; const diasRaw = container.dataset.dias; const dias = diasRaw !== '' && diasRaw !== undefined ? parseInt(diasRaw, 10) : null; const visivel = calcPadrao(isNaN(dias) ? null : dias); container.querySelectorAll('[data-chart-key]').forEach(col => { col.style.display = visivel.includes(col.dataset.chartKey) ? '' : 'none'; }); const statusData = JSON.parse(container.dataset.status || '{}'); const prioData = JSON.parse(container.dataset.prioridade || '{}'); const setorData = JSON.parse(container.dataset.setor || '{}'); const statusLabels = { atendido: 'Atendido', pendente: 'Pendente', em_andamento: 'Em Andamento', cancelado: 'Cancelado' }; const prioLabels = { baixa: 'Baixa', media: 'Média', alta: 'Alta', urgente: 'Urgente' }; const statusColors = { atendido: '#10B981', pendente: '#F59E0B', em_andamento: '#4F46E5', cancelado: '#6c757d' }; const prioColors = { baixa: '#10B981', media: '#F59E0B', alta: '#EF4444', urgente: '#8B5CF6' }; const palette = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#06B6D4', '#F97316', '#EC4899']; function pctTooltip(ctx) { const total = ctx.dataset.data.reduce((a, b) => a + b, 0); const pct = total > 0 ? ((ctx.parsed / total) * 100).toFixed(1) : 0; return ` ${ctx.label}: ${ctx.parsed} (${pct}%)`; } function buildDonut(id, raw, labelMap, colorMap) { const canvas = document.getElementById(id); if (!canvas) return; const entries = Object.entries(raw).filter(([, v]) => v > 0); if (!entries.length) return; _charts[id]?.destroy(); _charts[id] = new Chart(canvas, { type: 'doughnut', data: { labels: entries.map(([k]) => labelMap[k] ?? k), datasets: [{ data: entries.map(([, v]) => v), backgroundColor: entries.map(([k]) => colorMap[k] ?? '#ccc'), borderWidth: 2, borderColor: '#fff' }] }, options: { responsive: true, plugins: { legend: { position: 'bottom', labels: { font: { size: 12 }, padding: 12 } }, tooltip: { callbacks: { label: pctTooltip } } } } }); } function buildHBar(id, raw) { const canvas = document.getElementById(id); if (!canvas) return; const sorted = Object.entries(raw).sort((a, b) => b[1] - a[1]).slice(0, 10); if (!sorted.length) return; const total = sorted.reduce((a, [, v]) => a + v, 0); canvas.style.minHeight = Math.max(sorted.length * 32, 120) + 'px'; _charts[id]?.destroy(); _charts[id] = new Chart(canvas, { type: 'bar', data: { labels: sorted.map(([k]) => k), datasets: [{ data: sorted.map(([, v]) => v), backgroundColor: sorted.map((_, i) => palette[i % palette.length]), borderRadius: 4 }] }, options: { indexAxis: 'y', responsive: true, plugins: { legend: { display: false }, tooltip: { callbacks: { label: (ctx) => { const pct = total > 0 ? ((ctx.parsed.x / total) * 100).toFixed(1) : 0; return ` ${ctx.parsed.x} (${pct}%)`; } } } }, scales: { x: { beginAtZero: true, ticks: { precision: 0 } }, y: { ticks: { font: { size: 11 } } } } } }); } function buildVBar(id, labels, data, color) { const canvas = document.getElementById(id); if (!canvas) return; _charts[id]?.destroy(); _charts[id] = new Chart(canvas, { type: 'bar', data: { labels, datasets: [{ data, backgroundColor: color + 'cc', borderColor: color, borderWidth: 1, borderRadius: 4, }] }, options: { responsive: true, plugins: { legend: { display: false }, tooltip: { callbacks: { label: (ctx) => ` ${ctx.parsed.y} chamados` } } }, scales: { y: { beginAtZero: true, ticks: { precision: 0 } }, x: { ticks: { font: { size: 11 } } } } } }); } const mesLabels = JSON.parse(container.dataset.mesLabels || '[]'); const mesData = JSON.parse(container.dataset.mesData || '[]'); const semData = JSON.parse(container.dataset.semana || '{}'); buildDonut('chart-status', statusData, statusLabels, statusColors); buildDonut('chart-prioridade', prioData, prioLabels, prioColors); buildHBar('chart-setor', setorData); buildVBar('chart-mes', mesLabels, mesData, '#4F46E5'); buildVBar('chart-semana', Object.keys(semData), Object.values(semData), '#06B6D4'); } // ============ INIT ============ function init() { initFormsColapsaveis(); initSetorCollapses(); initCascata(); initCascataRelatorio(); initNovoCliente(); initClienteAutocomplete(); initParticipanteSearch(); initGerenciarClientes(); initGerenciarCollapse(); initRelatorioCharts(); atualizarTemposEmAndamento(); setInterval(atualizarTemposEmAndamento, 1000); } document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init(); })();
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings