Merge branch 'main' of https://git.depaula.app.br/Gabriel/portal_publico
This commit is contained in:
commit
aa417661f0
@ -17,6 +17,15 @@ STATUS_VENCE_30 = "vence_30_dias"
|
|||||||
STATUS_NO_PRAZO = "no_prazo"
|
STATUS_NO_PRAZO = "no_prazo"
|
||||||
STATUS_SEM_VENCIMENTO = "sem_vencimento"
|
STATUS_SEM_VENCIMENTO = "sem_vencimento"
|
||||||
|
|
||||||
|
# Não é uma classificação por-ação real (status_prazo() nunca retorna isto) —
|
||||||
|
# é só um nome de bucket agregado usado por `_filtra_status_prazo()`
|
||||||
|
# (portal_api/views.py) e pelo chip "Em aberto" do frontend, equivalente a
|
||||||
|
# "qualquer bucket exceto finalizada". NUNCA adicionar em STATUS_PRAZO_LABELS:
|
||||||
|
# esse dict é iterado pra montar os contadores do dashboard, uma contagem por
|
||||||
|
# classificação mutuamente exclusiva de ação — "não finalizada" não é
|
||||||
|
# mutuamente exclusiva com vencida/vence_7_dias/etc, é a união delas.
|
||||||
|
STATUS_NAO_FINALIZADA = "nao_finalizada"
|
||||||
|
|
||||||
STATUS_PRAZO_LABELS = {
|
STATUS_PRAZO_LABELS = {
|
||||||
STATUS_FINALIZADA: "Finalizada",
|
STATUS_FINALIZADA: "Finalizada",
|
||||||
STATUS_VENCIDA: "Vencida",
|
STATUS_VENCIDA: "Vencida",
|
||||||
|
|||||||
@ -2085,6 +2085,7 @@ class NCAcaoSerializer(serializers.ModelSerializer):
|
|||||||
# da Gestão e drill-down dos rankings do Dashboard) sem duplicar o dado.
|
# da Gestão e drill-down dos rankings do Dashboard) sem duplicar o dado.
|
||||||
ocorrencia_clientes = serializers.CharField(source="ocorrencia.clientes_relacionados", read_only=True)
|
ocorrencia_clientes = serializers.CharField(source="ocorrencia.clientes_relacionados", read_only=True)
|
||||||
ocorrencia_area = serializers.CharField(source="ocorrencia.area", read_only=True)
|
ocorrencia_area = serializers.CharField(source="ocorrencia.area", read_only=True)
|
||||||
|
ocorrencia_tipo_ocorrencia = serializers.CharField(source="ocorrencia.tipo_ocorrencia", read_only=True)
|
||||||
status_prazo = serializers.SerializerMethodField()
|
status_prazo = serializers.SerializerMethodField()
|
||||||
status_prazo_label = serializers.SerializerMethodField()
|
status_prazo_label = serializers.SerializerMethodField()
|
||||||
dias_para_vencer = serializers.SerializerMethodField()
|
dias_para_vencer = serializers.SerializerMethodField()
|
||||||
@ -2098,6 +2099,7 @@ class NCAcaoSerializer(serializers.ModelSerializer):
|
|||||||
"ocorrencia_assunto",
|
"ocorrencia_assunto",
|
||||||
"ocorrencia_clientes",
|
"ocorrencia_clientes",
|
||||||
"ocorrencia_area",
|
"ocorrencia_area",
|
||||||
|
"ocorrencia_tipo_ocorrencia",
|
||||||
"codigo",
|
"codigo",
|
||||||
"data_emissao",
|
"data_emissao",
|
||||||
"tipo_acao",
|
"tipo_acao",
|
||||||
|
|||||||
@ -3020,6 +3020,8 @@ def _filtra_status_prazo(queryset: QuerySet[NCAcao], bucket: str, hoje: date) ->
|
|||||||
return queryset.filter(data_finalizacao__isnull=False)
|
return queryset.filter(data_finalizacao__isnull=False)
|
||||||
|
|
||||||
queryset = queryset.filter(data_finalizacao__isnull=True)
|
queryset = queryset.filter(data_finalizacao__isnull=True)
|
||||||
|
if bucket == nc_classificacao.STATUS_NAO_FINALIZADA:
|
||||||
|
return queryset
|
||||||
if bucket == nc_classificacao.STATUS_VENCIDA:
|
if bucket == nc_classificacao.STATUS_VENCIDA:
|
||||||
return queryset.filter(vencimento_efetivo__lt=hoje)
|
return queryset.filter(vencimento_efetivo__lt=hoje)
|
||||||
if bucket == nc_classificacao.STATUS_VENCE_7:
|
if bucket == nc_classificacao.STATUS_VENCE_7:
|
||||||
|
|||||||
@ -144,6 +144,118 @@
|
|||||||
color: var(--accent-contrast);
|
color: var(--accent-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ordenação por clique no cabeçalho — mesma linguagem visual de #ua-table
|
||||||
|
(perfis-acesso.css, th[data-sort]/.ua-sort-icon), duplicado aqui porque
|
||||||
|
essa folha não compartilha código com a de usuários. Escopado pela classe
|
||||||
|
compartilhada .ncf-table-sortable (as 3 tabelas da Gestão), não por ID
|
||||||
|
individual. */
|
||||||
|
.ncf-table-sortable th[data-sort] {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-sort-icon {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-table-sortable th[data-sort].is-sort-asc .ncf-sort-icon,
|
||||||
|
.ncf-table-sortable th[data-sort].is-sort-desc .ncf-sort-icon {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filtro por coluna (só "Tipo de Ocorrência"/"Vencimento", tabela de Ações) —
|
||||||
|
mesmo espírito de .ind-info-wrap/.ind-info-popover (indicador-desempenho.css),
|
||||||
|
mas com uma diferença importante: aqui o popover é position:fixed (não
|
||||||
|
absolute), com top/left calculados em JS a partir do
|
||||||
|
getBoundingClientRect() do botão (ver pidNcfAbrirPopoverColuna() em
|
||||||
|
nao-conformidades.js). position:fixed escapa do overflow:hidden de
|
||||||
|
.pa-table-wrap (perfis-acesso.css, usado pra arredondar o canto da
|
||||||
|
tabela) sem precisar reabrir esse overflow — uma primeira versão fazia
|
||||||
|
isso com overflow:visible no wrapper, mas isso quebrava a contenção da
|
||||||
|
tabela (12 colunas já é bem larga) e estourava a largura da página. */
|
||||||
|
.ncf-th-filterable {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-th-sort-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-col-filtro-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-left: var(--space-2);
|
||||||
|
padding: 0;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
color: var(--text-muted);
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color var(--transition-fast), border-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-col-filtro-btn:hover,
|
||||||
|
.ncf-col-filtro-btn[aria-expanded="true"] {
|
||||||
|
color: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-col-filtro-popover {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 30;
|
||||||
|
width: 240px;
|
||||||
|
padding: var(--space-3);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
background: var(--bg-surface-raised);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
animation: pidFadeSlideUp 140ms ease;
|
||||||
|
/* .pa-table th força uppercase/letter-spacing/tamanho pequeno no
|
||||||
|
cabeçalho — resetado aqui pra virar texto normal de novo. */
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--text-primary);
|
||||||
|
white-space: normal;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-col-filtro-popover .checklist-box {
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-col-filtro-popover .modal-field {
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-col-filtro-popover__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-top: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-col-filtro-popover__actions .btn-outline,
|
||||||
|
.ncf-col-filtro-popover__actions .btn-solid {
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ação já finalizada no Sigsistem — não há nada pra Qualidade tratar, então
|
||||||
|
a coluna de ação mostra só um traço em vez do botão "Marcar como tratado". */
|
||||||
|
.ncf-sem-acao-necessaria {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
/* Dashboard */
|
/* Dashboard */
|
||||||
.ncf-dashboard-toolbar {
|
.ncf-dashboard-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -450,6 +562,32 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A linha principal de uma Ação não fecha com borda embaixo — quem fecha é
|
||||||
|
a linha de descrição logo abaixo (ver .ncf-acao-descricao-row), pra as
|
||||||
|
duas lerem como um bloco só, não duas linhas de tabela separadas. */
|
||||||
|
.pa-table tr.ncf-acao-main-row td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Linha full-width com a descrição da Ação, logo abaixo da linha principal
|
||||||
|
(não é mais uma coluna própria — não cabia na largura da tabela). Classe
|
||||||
|
própria com maior especificidade que .pa-table td:last-child
|
||||||
|
(text-align:right) pelo mesmo motivo de .ncf-detail-cell acima: como usa
|
||||||
|
colspan, é sempre "a última" célula da linha. */
|
||||||
|
.pa-table tr.ncf-acao-descricao-row td.ncf-acao-descricao-cell {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: var(--space-3);
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-acao-descricao-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.ncf-detail-panel {
|
.ncf-detail-panel {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
|||||||
@ -42,6 +42,68 @@ const PID_NCF_STATUS_PRAZO_CLASSES = {
|
|||||||
sem_vencimento: "status-pill status-pill--neutral",
|
sem_vencimento: "status-pill status-pill--neutral",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Compara duas strings tratando conteúdo numérico como número (ex.: "92" <
|
||||||
|
// "150"), senão cai pra comparação alfabética pt-BR — mesmo critério de
|
||||||
|
// comparaValoresUsuario() em users-admin.js/comparaValoresLista() em
|
||||||
|
// importacao-plano-saude.js, copiado localmente porque cada tela do Portal
|
||||||
|
// tem seu próprio arquivo JS autocontido, sem módulo compartilhado.
|
||||||
|
function pidNcfCompara(a, b) {
|
||||||
|
const an = Number(a);
|
||||||
|
const bn = Number(b);
|
||||||
|
const ambosNumeros = a !== "" && b !== "" && a != null && b != null && !Number.isNaN(an) && !Number.isNaN(bn);
|
||||||
|
if (ambosNumeros) return an - bn;
|
||||||
|
return String(a ?? "").localeCompare(String(b ?? ""), "pt-BR", { sensitivity: "base" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordena por severidade, não alfabeticamente (ex.: "Vencida" precisa vir
|
||||||
|
// antes de "No prazo", não é isso que localeCompare faria).
|
||||||
|
const PID_NCF_STATUS_PRAZO_RANK = {
|
||||||
|
vencida: 0,
|
||||||
|
vence_7_dias: 1,
|
||||||
|
vence_30_dias: 2,
|
||||||
|
no_prazo: 3,
|
||||||
|
sem_vencimento: 4,
|
||||||
|
finalizada: 5,
|
||||||
|
};
|
||||||
|
const PID_NCF_STATUS_TRATATIVA_RANK = { pendente: 0, tratado: 1 };
|
||||||
|
|
||||||
|
function pidNcfAplicarSort(itens, sortKey, sortDir, sortValorMap) {
|
||||||
|
if (!sortKey) return itens; // preserva a ordem que já veio do servidor até o usuário clicar um cabeçalho
|
||||||
|
const extrator = sortValorMap[sortKey];
|
||||||
|
if (!extrator) return itens;
|
||||||
|
return itens.slice().sort((a, b) => pidNcfCompara(extrator(a), extrator(b)) * sortDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pidNcfAtualizarSortHeaders(headers, sortKey, sortDir) {
|
||||||
|
headers.forEach((th) => {
|
||||||
|
const key = th.getAttribute("data-sort");
|
||||||
|
th.classList.toggle("is-sort-asc", key === sortKey && sortDir === 1);
|
||||||
|
th.classList.toggle("is-sort-desc", key === sortKey && sortDir === -1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liga o clique de ordenação nos <th data-sort> de uma tabela — clicar
|
||||||
|
// alterna asc/desc na mesma coluna ou reseta pra asc numa coluna nova. Ignora
|
||||||
|
// cliques que vieram do ícone/popover de filtro de coluna (só a tabela de
|
||||||
|
// Ações tem isso hoje), pra sort e filtro de coluna nunca disparar juntos.
|
||||||
|
function pidNcfBindSortHeaders(tableId, getSortState, setSortState, rerender) {
|
||||||
|
const headers = Array.from(document.querySelectorAll(`#${tableId} th[data-sort]`));
|
||||||
|
headers.forEach((th) => {
|
||||||
|
th.addEventListener("click", (evento) => {
|
||||||
|
if (evento.target.closest("[data-ncf-col-filtro], .ncf-col-filtro-popover")) return;
|
||||||
|
const key = th.getAttribute("data-sort");
|
||||||
|
const { sortKey, sortDir } = getSortState();
|
||||||
|
if (sortKey === key) {
|
||||||
|
setSortState(key, sortDir * -1);
|
||||||
|
} else {
|
||||||
|
setSortState(key, 1);
|
||||||
|
}
|
||||||
|
rerender();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
/** Botão de expandir (chevron que gira) + linha de detalhe associada, mesmo
|
/** Botão de expandir (chevron que gira) + linha de detalhe associada, mesmo
|
||||||
* padrão de `.cc-painel-fiscal__toggle` (custo-contratacao.js), adaptado pra
|
* padrão de `.cc-painel-fiscal__toggle` (custo-contratacao.js), adaptado pra
|
||||||
* uma linha de tabela — `montarConteudo(painelEl)` só roda na primeira vez
|
* uma linha de tabela — `montarConteudo(painelEl)` só roda na primeira vez
|
||||||
@ -153,14 +215,39 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
let semAnaliseTodos = [];
|
let semAnaliseTodos = [];
|
||||||
let analiseSemAcaoTodos = [];
|
let analiseSemAcaoTodos = [];
|
||||||
let acoesTodos = [];
|
let acoesTodos = [];
|
||||||
let prazoFiltroAtual = "todas";
|
// "nao_finalizada" (não "todas") é o padrão — a tela deve abrir só com o
|
||||||
|
// que ainda está em aberto (data_finalizacao vazia no Sigsistem), pedido
|
||||||
|
// explícito do usuário; "Todas" continua disponível como chip separado.
|
||||||
|
let prazoFiltroAtual = "nao_finalizada";
|
||||||
let tratativaFiltroAtual = "pendente";
|
let tratativaFiltroAtual = "pendente";
|
||||||
|
let semAnaliseTratativaFiltroAtual = "pendente";
|
||||||
|
let analiseSemAcaoTratativaFiltroAtual = "pendente";
|
||||||
let buscaGestao = "";
|
let buscaGestao = "";
|
||||||
|
|
||||||
|
// Ordenação por clique no cabeçalho — estado independente por tabela.
|
||||||
|
// sortKey=null preserva a ordenação vinda do servidor até o usuário clicar
|
||||||
|
// um cabeçalho pela primeira vez.
|
||||||
|
let semAnaliseSortKey = null;
|
||||||
|
let semAnaliseSortDir = 1;
|
||||||
|
let analiseSemAcaoSortKey = null;
|
||||||
|
let analiseSemAcaoSortDir = 1;
|
||||||
|
let acoesSortKey = null;
|
||||||
|
let acoesSortDir = 1;
|
||||||
|
|
||||||
|
// Filtros de coluna da tabela de Ações (só "Tipo de Ocorrência" e
|
||||||
|
// "Vencimento" têm isso, e só nessa tabela) — sempre client-side, sobre o
|
||||||
|
// `acoesTodos` já carregado (ver carregarAcoes()/renderAcoes()).
|
||||||
|
let tipoOcorrenciaFiltroSelecionados = new Set(); // vazio = sem filtro (mostra tudo)
|
||||||
|
let tipoOcorrenciaOpcoesDisponiveis = [];
|
||||||
|
let vencimentoFiltroDe = "";
|
||||||
|
let vencimentoFiltroAte = "";
|
||||||
|
|
||||||
const gestaoSearch = document.getElementById("ncf-gestao-search");
|
const gestaoSearch = document.getElementById("ncf-gestao-search");
|
||||||
const gestaoSearchClearBtn = document.getElementById("ncf-gestao-search-clear-btn");
|
const gestaoSearchClearBtn = document.getElementById("ncf-gestao-search-clear-btn");
|
||||||
const filtroPrazoEl = document.getElementById("ncf-filtro-prazo");
|
const filtroPrazoEl = document.getElementById("ncf-filtro-prazo");
|
||||||
const filtroTratativaEl = document.getElementById("ncf-filtro-tratativa");
|
const filtroTratativaEl = document.getElementById("ncf-filtro-tratativa");
|
||||||
|
const filtroTratativaSemAnaliseEl = document.getElementById("ncf-filtro-tratativa-sem-analise");
|
||||||
|
const filtroTratativaAnaliseSemAcaoEl = document.getElementById("ncf-filtro-tratativa-analise-sem-acao");
|
||||||
|
|
||||||
function filtrarPorBusca(itens, campos) {
|
function filtrarPorBusca(itens, campos) {
|
||||||
const termo = buscaGestao.trim().toLowerCase();
|
const termo = buscaGestao.trim().toLowerCase();
|
||||||
@ -206,6 +293,20 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
atualizarChipsAtivos(filtroTratativaEl, "ncfTratativa", tratativaFiltroAtual);
|
atualizarChipsAtivos(filtroTratativaEl, "ncfTratativa", tratativaFiltroAtual);
|
||||||
carregarAcoes();
|
carregarAcoes();
|
||||||
});
|
});
|
||||||
|
filtroTratativaSemAnaliseEl.addEventListener("click", (evento) => {
|
||||||
|
const btn = evento.target.closest("[data-ncf-tratativa]");
|
||||||
|
if (!btn) return;
|
||||||
|
semAnaliseTratativaFiltroAtual = btn.dataset.ncfTratativa;
|
||||||
|
atualizarChipsAtivos(filtroTratativaSemAnaliseEl, "ncfTratativa", semAnaliseTratativaFiltroAtual);
|
||||||
|
carregarSemAnalise();
|
||||||
|
});
|
||||||
|
filtroTratativaAnaliseSemAcaoEl.addEventListener("click", (evento) => {
|
||||||
|
const btn = evento.target.closest("[data-ncf-tratativa]");
|
||||||
|
if (!btn) return;
|
||||||
|
analiseSemAcaoTratativaFiltroAtual = btn.dataset.ncfTratativa;
|
||||||
|
atualizarChipsAtivos(filtroTratativaAnaliseSemAcaoEl, "ncfTratativa", analiseSemAcaoTratativaFiltroAtual);
|
||||||
|
carregarAnaliseSemAcao();
|
||||||
|
});
|
||||||
|
|
||||||
async function garantirGestaoCarregada() {
|
async function garantirGestaoCarregada() {
|
||||||
if (gestaoCarregada) return;
|
if (gestaoCarregada) return;
|
||||||
@ -217,20 +318,41 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const semAnaliseBody = document.getElementById("ncf-sem-analise-body");
|
const semAnaliseBody = document.getElementById("ncf-sem-analise-body");
|
||||||
const semAnaliseEmpty = document.getElementById("ncf-sem-analise-empty");
|
const semAnaliseEmpty = document.getElementById("ncf-sem-analise-empty");
|
||||||
const semAnaliseCount = document.getElementById("ncf-sem-analise-count");
|
const semAnaliseCount = document.getElementById("ncf-sem-analise-count");
|
||||||
|
const PID_NCF_SEM_ANALISE_SORT_VALOR = {
|
||||||
|
codigo: (oc) => oc.codigo,
|
||||||
|
data_emissao: (oc) => oc.data_emissao || "",
|
||||||
|
area: (oc) => oc.area || "",
|
||||||
|
emissor_relato: (oc) => oc.emissor_relato || "",
|
||||||
|
indicado_analise: (oc) => oc.indicado_analise || "",
|
||||||
|
status_tratativa: (oc) => PID_NCF_STATUS_TRATATIVA_RANK[oc.status_tratativa] ?? 0,
|
||||||
|
};
|
||||||
|
const semAnaliseSortHeaders = pidNcfBindSortHeaders(
|
||||||
|
"ncf-table-sem-analise",
|
||||||
|
() => ({ sortKey: semAnaliseSortKey, sortDir: semAnaliseSortDir }),
|
||||||
|
(key, dir) => {
|
||||||
|
semAnaliseSortKey = key;
|
||||||
|
semAnaliseSortDir = dir;
|
||||||
|
},
|
||||||
|
renderSemAnalise
|
||||||
|
);
|
||||||
|
|
||||||
async function carregarSemAnalise() {
|
async function carregarSemAnalise() {
|
||||||
semAnaliseTodos = await pidApiRequest("/nao-conformidades/ocorrencias/?sem_analise=true");
|
const params = new URLSearchParams({ sem_analise: "true" });
|
||||||
|
if (semAnaliseTratativaFiltroAtual !== "todas") params.set("status_tratativa", semAnaliseTratativaFiltroAtual);
|
||||||
|
semAnaliseTodos = await pidApiRequest(`/nao-conformidades/ocorrencias/?${params.toString()}`);
|
||||||
renderSemAnalise();
|
renderSemAnalise();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSemAnalise() {
|
function renderSemAnalise() {
|
||||||
const itens = filtrarPorBusca(semAnaliseTodos, [
|
let itens = filtrarPorBusca(semAnaliseTodos, [
|
||||||
"indicado_analise",
|
"indicado_analise",
|
||||||
"responsavel_analise",
|
"responsavel_analise",
|
||||||
"clientes_relacionados",
|
"clientes_relacionados",
|
||||||
"assunto",
|
"assunto",
|
||||||
"emissor_relato",
|
"emissor_relato",
|
||||||
]);
|
]);
|
||||||
|
itens = pidNcfAplicarSort(itens, semAnaliseSortKey, semAnaliseSortDir, PID_NCF_SEM_ANALISE_SORT_VALOR);
|
||||||
|
pidNcfAtualizarSortHeaders(semAnaliseSortHeaders, semAnaliseSortKey, semAnaliseSortDir);
|
||||||
pidNcfAtualizarContador(semAnaliseCount, itens.length);
|
pidNcfAtualizarContador(semAnaliseCount, itens.length);
|
||||||
semAnaliseBody.innerHTML = "";
|
semAnaliseBody.innerHTML = "";
|
||||||
semAnaliseEmpty.hidden = itens.length > 0;
|
semAnaliseEmpty.hidden = itens.length > 0;
|
||||||
@ -272,20 +394,41 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const analiseSemAcaoBody = document.getElementById("ncf-analise-sem-acao-body");
|
const analiseSemAcaoBody = document.getElementById("ncf-analise-sem-acao-body");
|
||||||
const analiseSemAcaoEmpty = document.getElementById("ncf-analise-sem-acao-empty");
|
const analiseSemAcaoEmpty = document.getElementById("ncf-analise-sem-acao-empty");
|
||||||
const analiseSemAcaoCount = document.getElementById("ncf-analise-sem-acao-count");
|
const analiseSemAcaoCount = document.getElementById("ncf-analise-sem-acao-count");
|
||||||
|
const PID_NCF_ANALISE_SEM_ACAO_SORT_VALOR = {
|
||||||
|
codigo: (oc) => oc.codigo,
|
||||||
|
data_emissao: (oc) => oc.data_emissao || "",
|
||||||
|
area: (oc) => oc.area || "",
|
||||||
|
responsavel_analise: (oc) => oc.responsavel_analise || "",
|
||||||
|
descricao_analise: (oc) => oc.descricao_analise || "",
|
||||||
|
status_tratativa: (oc) => PID_NCF_STATUS_TRATATIVA_RANK[oc.status_tratativa] ?? 0,
|
||||||
|
};
|
||||||
|
const analiseSemAcaoSortHeaders = pidNcfBindSortHeaders(
|
||||||
|
"ncf-table-analise-sem-acao",
|
||||||
|
() => ({ sortKey: analiseSemAcaoSortKey, sortDir: analiseSemAcaoSortDir }),
|
||||||
|
(key, dir) => {
|
||||||
|
analiseSemAcaoSortKey = key;
|
||||||
|
analiseSemAcaoSortDir = dir;
|
||||||
|
},
|
||||||
|
renderAnaliseSemAcao
|
||||||
|
);
|
||||||
|
|
||||||
async function carregarAnaliseSemAcao() {
|
async function carregarAnaliseSemAcao() {
|
||||||
analiseSemAcaoTodos = await pidApiRequest("/nao-conformidades/ocorrencias/?analise_sem_acao=true");
|
const params = new URLSearchParams({ analise_sem_acao: "true" });
|
||||||
|
if (analiseSemAcaoTratativaFiltroAtual !== "todas") params.set("status_tratativa", analiseSemAcaoTratativaFiltroAtual);
|
||||||
|
analiseSemAcaoTodos = await pidApiRequest(`/nao-conformidades/ocorrencias/?${params.toString()}`);
|
||||||
renderAnaliseSemAcao();
|
renderAnaliseSemAcao();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAnaliseSemAcao() {
|
function renderAnaliseSemAcao() {
|
||||||
const itens = filtrarPorBusca(analiseSemAcaoTodos, [
|
let itens = filtrarPorBusca(analiseSemAcaoTodos, [
|
||||||
"indicado_analise",
|
"indicado_analise",
|
||||||
"responsavel_analise",
|
"responsavel_analise",
|
||||||
"clientes_relacionados",
|
"clientes_relacionados",
|
||||||
"assunto",
|
"assunto",
|
||||||
"descricao_analise",
|
"descricao_analise",
|
||||||
]);
|
]);
|
||||||
|
itens = pidNcfAplicarSort(itens, analiseSemAcaoSortKey, analiseSemAcaoSortDir, PID_NCF_ANALISE_SEM_ACAO_SORT_VALOR);
|
||||||
|
pidNcfAtualizarSortHeaders(analiseSemAcaoSortHeaders, analiseSemAcaoSortKey, analiseSemAcaoSortDir);
|
||||||
pidNcfAtualizarContador(analiseSemAcaoCount, itens.length);
|
pidNcfAtualizarContador(analiseSemAcaoCount, itens.length);
|
||||||
analiseSemAcaoBody.innerHTML = "";
|
analiseSemAcaoBody.innerHTML = "";
|
||||||
analiseSemAcaoEmpty.hidden = itens.length > 0;
|
analiseSemAcaoEmpty.hidden = itens.length > 0;
|
||||||
@ -381,6 +524,26 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const acoesBody = document.getElementById("ncf-acoes-body");
|
const acoesBody = document.getElementById("ncf-acoes-body");
|
||||||
const acoesEmpty = document.getElementById("ncf-acoes-empty");
|
const acoesEmpty = document.getElementById("ncf-acoes-empty");
|
||||||
const acoesCount = document.getElementById("ncf-acoes-count");
|
const acoesCount = document.getElementById("ncf-acoes-count");
|
||||||
|
const PID_NCF_ACOES_SORT_VALOR = {
|
||||||
|
ocorrencia_codigo: (a) => Number(a.ocorrencia_codigo) * 1000000 + Number(a.codigo || 0),
|
||||||
|
ocorrencia_area: (a) => a.ocorrencia_area || "",
|
||||||
|
tipo_acao: (a) => a.tipo_acao || "",
|
||||||
|
ocorrencia_tipo_ocorrencia: (a) => a.ocorrencia_tipo_ocorrencia || "",
|
||||||
|
executor: (a) => a.executor || "",
|
||||||
|
vencimento_efetivo: (a) => a.vencimento_efetivo || "",
|
||||||
|
ultimo_acompanhamento_em: (a) => a.ultimo_acompanhamento_em || "",
|
||||||
|
status_prazo: (a) => PID_NCF_STATUS_PRAZO_RANK[a.status_prazo] ?? 99,
|
||||||
|
status_tratativa: (a) => PID_NCF_STATUS_TRATATIVA_RANK[a.status_tratativa] ?? 0,
|
||||||
|
};
|
||||||
|
const acoesSortHeaders = pidNcfBindSortHeaders(
|
||||||
|
"ncf-table-acoes",
|
||||||
|
() => ({ sortKey: acoesSortKey, sortDir: acoesSortDir }),
|
||||||
|
(key, dir) => {
|
||||||
|
acoesSortKey = key;
|
||||||
|
acoesSortDir = dir;
|
||||||
|
},
|
||||||
|
renderAcoes
|
||||||
|
);
|
||||||
|
|
||||||
async function carregarAcoes() {
|
async function carregarAcoes() {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
@ -388,31 +551,65 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
if (tratativaFiltroAtual !== "todas") params.set("status_tratativa", tratativaFiltroAtual);
|
if (tratativaFiltroAtual !== "todas") params.set("status_tratativa", tratativaFiltroAtual);
|
||||||
const query = params.toString();
|
const query = params.toString();
|
||||||
acoesTodos = await pidApiRequest(`/nao-conformidades/acoes/${query ? "?" + query : ""}`);
|
acoesTodos = await pidApiRequest(`/nao-conformidades/acoes/${query ? "?" + query : ""}`);
|
||||||
|
|
||||||
|
// Opções do checklist "Tipo de Ocorrência" vêm do array cheio, ANTES de
|
||||||
|
// qualquer filtro de coluna/busca — recalculado só aqui (uma vez por
|
||||||
|
// carregamento do servidor), nunca dentro de renderAcoes(), pra
|
||||||
|
// desmarcar um tipo nunca fazer os outros sumirem da lista.
|
||||||
|
tipoOcorrenciaOpcoesDisponiveis = Array.from(
|
||||||
|
new Set(acoesTodos.map((a) => a.ocorrencia_tipo_ocorrencia).filter(Boolean))
|
||||||
|
).sort((a, b) => a.localeCompare(b, "pt-BR", { sensitivity: "base" }));
|
||||||
|
tipoOcorrenciaFiltroSelecionados = new Set(
|
||||||
|
Array.from(tipoOcorrenciaFiltroSelecionados).filter((v) => tipoOcorrenciaOpcoesDisponiveis.includes(v))
|
||||||
|
);
|
||||||
|
renderTipoOcorrenciaChecklist();
|
||||||
|
|
||||||
renderAcoes();
|
renderAcoes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pidNcfFiltraColunaAcoes(itens) {
|
||||||
|
let out = itens;
|
||||||
|
if (tipoOcorrenciaFiltroSelecionados.size > 0) {
|
||||||
|
out = out.filter((a) => tipoOcorrenciaFiltroSelecionados.has(a.ocorrencia_tipo_ocorrencia));
|
||||||
|
}
|
||||||
|
if (vencimentoFiltroDe) out = out.filter((a) => a.vencimento_efetivo && a.vencimento_efetivo >= vencimentoFiltroDe);
|
||||||
|
if (vencimentoFiltroAte) out = out.filter((a) => a.vencimento_efetivo && a.vencimento_efetivo <= vencimentoFiltroAte);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function renderAcoes() {
|
function renderAcoes() {
|
||||||
const itens = filtrarPorBusca(acoesTodos, ["executor", "ocorrencia_clientes", "ocorrencia_codigo", "acao_texto"]);
|
let itens = filtrarPorBusca(acoesTodos, ["executor", "ocorrencia_clientes", "ocorrencia_codigo", "acao_texto"]);
|
||||||
|
itens = pidNcfFiltraColunaAcoes(itens);
|
||||||
|
itens = pidNcfAplicarSort(itens, acoesSortKey, acoesSortDir, PID_NCF_ACOES_SORT_VALOR);
|
||||||
|
pidNcfAtualizarSortHeaders(acoesSortHeaders, acoesSortKey, acoesSortDir);
|
||||||
pidNcfAtualizarContador(acoesCount, itens.length);
|
pidNcfAtualizarContador(acoesCount, itens.length);
|
||||||
acoesBody.innerHTML = "";
|
acoesBody.innerHTML = "";
|
||||||
acoesEmpty.hidden = itens.length > 0;
|
acoesEmpty.hidden = itens.length > 0;
|
||||||
itens.forEach((acaoItem) => {
|
itens.forEach((acaoItem) => {
|
||||||
const tratado = acaoItem.status_tratativa === "tratado";
|
const tratado = acaoItem.status_tratativa === "tratado";
|
||||||
const { btn, trDetalhe } = pidNcfCriarLinhaExpandivel(10, (painel) => montarPainelAcao(painel, acaoItem));
|
// 11 colunas — a descrição da Ação não é mais uma coluna própria (não
|
||||||
|
// cabia mais na largura da tabela junto com as outras); vira uma linha
|
||||||
|
// full-width logo abaixo, ver trDescricao mais adiante.
|
||||||
|
const { btn, trDetalhe } = pidNcfCriarLinhaExpandivel(11, (painel) => montarPainelAcao(painel, acaoItem));
|
||||||
|
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
|
tr.className = "ncf-acao-main-row";
|
||||||
const tdBtn = document.createElement("td");
|
const tdBtn = document.createElement("td");
|
||||||
tdBtn.className = "ncf-expand-col";
|
tdBtn.className = "ncf-expand-col";
|
||||||
tdBtn.appendChild(btn);
|
tdBtn.appendChild(btn);
|
||||||
tr.appendChild(tdBtn);
|
tr.appendChild(tdBtn);
|
||||||
|
const ultimoAcompHtml = acaoItem.ultimo_acompanhamento_eh_prorrogacao
|
||||||
|
? ` <span class="status-pill status-pill--warning">Prorrogação</span>`
|
||||||
|
: "";
|
||||||
tr.insertAdjacentHTML(
|
tr.insertAdjacentHTML(
|
||||||
"beforeend",
|
"beforeend",
|
||||||
`<td>${acaoItem.ocorrencia_codigo}/${acaoItem.codigo}</td>
|
`<td>${acaoItem.ocorrencia_codigo}/${acaoItem.codigo}</td>
|
||||||
<td>${pidNcfEscapeHtml(acaoItem.ocorrencia_area) || "—"}</td>
|
<td>${pidNcfEscapeHtml(acaoItem.ocorrencia_area) || "—"}</td>
|
||||||
<td>${pidNcfEscapeHtml(acaoItem.tipo_acao) || "—"}</td>
|
<td>${pidNcfEscapeHtml(acaoItem.tipo_acao) || "—"}</td>
|
||||||
<td>${pidNcfEscapeHtml((acaoItem.acao_texto || "").slice(0, 90))}</td>
|
<td>${pidNcfEscapeHtml(acaoItem.ocorrencia_tipo_ocorrencia) || "—"}</td>
|
||||||
<td>${pidNcfEscapeHtml(acaoItem.executor) || "—"}</td>
|
<td>${pidNcfEscapeHtml(acaoItem.executor) || "—"}</td>
|
||||||
<td>${pidNcfFormatDate(acaoItem.vencimento_efetivo)}</td>`
|
<td>${pidNcfFormatDate(acaoItem.vencimento_efetivo)}</td>
|
||||||
|
<td>${pidNcfFormatDate(acaoItem.ultimo_acompanhamento_em)}${ultimoAcompHtml}</td>`
|
||||||
);
|
);
|
||||||
const tdPrazo = document.createElement("td");
|
const tdPrazo = document.createElement("td");
|
||||||
const badgePrazo = document.createElement("span");
|
const badgePrazo = document.createElement("span");
|
||||||
@ -426,15 +623,37 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
tr.appendChild(tdTratativa);
|
tr.appendChild(tdTratativa);
|
||||||
|
|
||||||
const tdAcao = document.createElement("td");
|
const tdAcao = document.createElement("td");
|
||||||
const tratarBtn = document.createElement("button");
|
// Ação já finalizada no Sigsistem (data_finalizacao preenchida) — não
|
||||||
tratarBtn.type = "button";
|
// há nada pra Qualidade tratar aqui, então nem oferece o botão.
|
||||||
tratarBtn.className = tratado ? "btn-outline" : "btn-solid";
|
if (acaoItem.status_prazo === "finalizada") {
|
||||||
tratarBtn.textContent = tratado ? "Reabrir" : "Marcar como tratado";
|
const semAcaoEl = document.createElement("span");
|
||||||
tratarBtn.addEventListener("click", () => alternarTratativaAcao(acaoItem.id, tratado));
|
semAcaoEl.className = "ncf-sem-acao-necessaria";
|
||||||
tdAcao.appendChild(tratarBtn);
|
semAcaoEl.textContent = "—";
|
||||||
|
tdAcao.appendChild(semAcaoEl);
|
||||||
|
} else {
|
||||||
|
const tratarBtn = document.createElement("button");
|
||||||
|
tratarBtn.type = "button";
|
||||||
|
tratarBtn.className = tratado ? "btn-outline" : "btn-solid";
|
||||||
|
tratarBtn.textContent = tratado ? "Reabrir" : "Marcar como tratado";
|
||||||
|
tratarBtn.addEventListener("click", () => alternarTratativaAcao(acaoItem.id, tratado));
|
||||||
|
tdAcao.appendChild(tratarBtn);
|
||||||
|
}
|
||||||
tr.appendChild(tdAcao);
|
tr.appendChild(tdAcao);
|
||||||
|
|
||||||
|
// Linha full-width com a descrição da Ação — não é mais uma coluna
|
||||||
|
// própria (não cabia na largura da tabela junto com as outras 10
|
||||||
|
// colunas), mas o usuário precisa ver o texto sem abrir linha por
|
||||||
|
// linha, então vira uma segunda linha logo abaixo da principal.
|
||||||
|
const trDescricao = document.createElement("tr");
|
||||||
|
trDescricao.className = "ncf-acao-descricao-row";
|
||||||
|
const tdDescricao = document.createElement("td");
|
||||||
|
tdDescricao.colSpan = 11;
|
||||||
|
tdDescricao.className = "ncf-acao-descricao-cell";
|
||||||
|
tdDescricao.innerHTML = `<span class="ncf-acao-descricao-label">Ação:</span> ${pidNcfEscapeHtml(acaoItem.acao_texto) || "—"}`;
|
||||||
|
trDescricao.appendChild(tdDescricao);
|
||||||
|
|
||||||
acoesBody.appendChild(tr);
|
acoesBody.appendChild(tr);
|
||||||
|
acoesBody.appendChild(trDescricao);
|
||||||
acoesBody.appendChild(trDetalhe);
|
acoesBody.appendChild(trDetalhe);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -506,6 +725,100 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------- Filtro de coluna (Tipo de Ocorrência / Vencimento) ----------------
|
||||||
|
// Só existe na tabela de Ações, client-side, sobre `acoesTodos` já
|
||||||
|
// carregado — ver pidNcfFiltraColunaAcoes()/carregarAcoes() acima.
|
||||||
|
function renderTipoOcorrenciaChecklist() {
|
||||||
|
const lista = document.getElementById("ncf-col-filtro-tipo-lista");
|
||||||
|
const empty = document.getElementById("ncf-col-filtro-tipo-empty");
|
||||||
|
empty.hidden = tipoOcorrenciaOpcoesDisponiveis.length > 0;
|
||||||
|
lista.innerHTML = tipoOcorrenciaOpcoesDisponiveis
|
||||||
|
.map(
|
||||||
|
(tipo) => `
|
||||||
|
<label class="checklist-item">
|
||||||
|
<input type="checkbox" value="${pidNcfEscapeHtml(tipo)}" ${tipoOcorrenciaFiltroSelecionados.has(tipo) ? "checked" : ""} />
|
||||||
|
<span>${pidNcfEscapeHtml(tipo)}</span>
|
||||||
|
</label>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const acoesTableEl = document.getElementById("ncf-table-acoes");
|
||||||
|
|
||||||
|
function pidNcfFecharPopoversColuna() {
|
||||||
|
acoesTableEl.querySelectorAll(".ncf-col-filtro-popover").forEach((p) => (p.hidden = true));
|
||||||
|
acoesTableEl.querySelectorAll("[data-ncf-col-filtro]").forEach((b) => b.setAttribute("aria-expanded", "false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
acoesTableEl.addEventListener("click", (evento) => {
|
||||||
|
const filtroBtn = evento.target.closest("[data-ncf-col-filtro]");
|
||||||
|
if (filtroBtn) {
|
||||||
|
const popover = filtroBtn.nextElementSibling; // .ncf-col-filtro-popover
|
||||||
|
const jaAberto = !popover.hidden;
|
||||||
|
pidNcfFecharPopoversColuna();
|
||||||
|
if (!jaAberto) {
|
||||||
|
// position:fixed calculado a partir do botão — escapa do
|
||||||
|
// overflow:hidden de .pa-table-wrap sem precisar reabri-lo (ver
|
||||||
|
// nao-conformidades.css).
|
||||||
|
const rect = filtroBtn.getBoundingClientRect();
|
||||||
|
// Impede o popover (240px) de vazar pra fora da borda direita da
|
||||||
|
// janela quando o botão está perto dela.
|
||||||
|
const left = Math.min(rect.left, window.innerWidth - 256);
|
||||||
|
popover.style.top = `${rect.bottom + 6}px`;
|
||||||
|
popover.style.left = `${Math.max(left, 8)}px`;
|
||||||
|
}
|
||||||
|
popover.hidden = jaAberto;
|
||||||
|
filtroBtn.setAttribute("aria-expanded", String(!jaAberto));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aplicarBtn = evento.target.closest("[data-ncf-col-filtro-aplicar]");
|
||||||
|
if (aplicarBtn) {
|
||||||
|
const alvo = aplicarBtn.getAttribute("data-ncf-col-filtro-aplicar");
|
||||||
|
if (alvo === "tipo_ocorrencia") {
|
||||||
|
const marcados = Array.from(document.querySelectorAll("#ncf-col-filtro-tipo-lista input:checked")).map(
|
||||||
|
(cb) => cb.value
|
||||||
|
);
|
||||||
|
tipoOcorrenciaFiltroSelecionados = new Set(marcados);
|
||||||
|
} else if (alvo === "vencimento") {
|
||||||
|
vencimentoFiltroDe = document.getElementById("ncf-col-filtro-vencimento-de").value;
|
||||||
|
vencimentoFiltroAte = document.getElementById("ncf-col-filtro-vencimento-ate").value;
|
||||||
|
}
|
||||||
|
aplicarBtn.closest(".ncf-col-filtro-popover").hidden = true;
|
||||||
|
renderAcoes();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const limparBtn = evento.target.closest("[data-ncf-col-filtro-limpar]");
|
||||||
|
if (limparBtn) {
|
||||||
|
const alvo = limparBtn.getAttribute("data-ncf-col-filtro-limpar");
|
||||||
|
if (alvo === "tipo_ocorrencia") {
|
||||||
|
tipoOcorrenciaFiltroSelecionados = new Set();
|
||||||
|
renderTipoOcorrenciaChecklist();
|
||||||
|
} else if (alvo === "vencimento") {
|
||||||
|
vencimentoFiltroDe = "";
|
||||||
|
vencimentoFiltroAte = "";
|
||||||
|
document.getElementById("ncf-col-filtro-vencimento-de").value = "";
|
||||||
|
document.getElementById("ncf-col-filtro-vencimento-ate").value = "";
|
||||||
|
}
|
||||||
|
limparBtn.closest(".ncf-col-filtro-popover").hidden = true;
|
||||||
|
renderAcoes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", (evento) => {
|
||||||
|
if (evento.target.closest(".ncf-th-filterable")) return;
|
||||||
|
pidNcfFecharPopoversColuna();
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", (evento) => {
|
||||||
|
if (evento.key !== "Escape") return;
|
||||||
|
pidNcfFecharPopoversColuna();
|
||||||
|
});
|
||||||
|
// position:fixed não acompanha o scroll sozinho — mais simples fechar o
|
||||||
|
// popover do que recalcular a posição a cada evento de scroll/resize.
|
||||||
|
window.addEventListener("scroll", () => pidNcfFecharPopoversColuna(), true);
|
||||||
|
window.addEventListener("resize", () => pidNcfFecharPopoversColuna());
|
||||||
|
|
||||||
// ---------------- Drill-down do Dashboard pra Gestão ----------------
|
// ---------------- Drill-down do Dashboard pra Gestão ----------------
|
||||||
async function irParaGestaoComBusca(valor) {
|
async function irParaGestaoComBusca(valor) {
|
||||||
ativarTab("gestao");
|
ativarTab("gestao");
|
||||||
@ -526,6 +839,16 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
// tinha, recarrega só as Ações (sem repetir Sem Análise/Análise sem Ação).
|
// tinha, recarrega só as Ações (sem repetir Sem Análise/Análise sem Ação).
|
||||||
prazoFiltroAtual = bucket;
|
prazoFiltroAtual = bucket;
|
||||||
tratativaFiltroAtual = "todas";
|
tratativaFiltroAtual = "todas";
|
||||||
|
// Zera os filtros de coluna (Tipo de Ocorrência/Vencimento) — é uma
|
||||||
|
// navegação "leve a tela pra este estado", não deveria carregar filtro
|
||||||
|
// residual de uma sessão anterior escondendo o que o card prometeu mostrar.
|
||||||
|
tipoOcorrenciaFiltroSelecionados = new Set();
|
||||||
|
vencimentoFiltroDe = "";
|
||||||
|
vencimentoFiltroAte = "";
|
||||||
|
const vencimentoDeInput = document.getElementById("ncf-col-filtro-vencimento-de");
|
||||||
|
const vencimentoAteInput = document.getElementById("ncf-col-filtro-vencimento-ate");
|
||||||
|
if (vencimentoDeInput) vencimentoDeInput.value = "";
|
||||||
|
if (vencimentoAteInput) vencimentoAteInput.value = "";
|
||||||
atualizarChipsAtivos(filtroPrazoEl, "ncfPrazo", bucket);
|
atualizarChipsAtivos(filtroPrazoEl, "ncfPrazo", bucket);
|
||||||
atualizarChipsAtivos(filtroTratativaEl, "ncfTratativa", "todas");
|
atualizarChipsAtivos(filtroTratativaEl, "ncfTratativa", "todas");
|
||||||
ativarTab("gestao");
|
ativarTab("gestao");
|
||||||
|
|||||||
@ -409,17 +409,25 @@
|
|||||||
<span class="ncf-count" id="ncf-sem-analise-count"></span>
|
<span class="ncf-count" id="ncf-sem-analise-count"></span>
|
||||||
</div>
|
</div>
|
||||||
<p class="ncf-section__hint">Ocorrências abertas sem nenhuma análise registrada — cobrar o responsável indicado.</p>
|
<p class="ncf-section__hint">Ocorrências abertas sem nenhuma análise registrada — cobrar o responsável indicado.</p>
|
||||||
|
<div class="ncf-filtro-grupo">
|
||||||
|
<span class="ncf-filtro-grupo__label">Tratativa:</span>
|
||||||
|
<div class="ncf-chip-row" id="ncf-filtro-tratativa-sem-analise">
|
||||||
|
<button type="button" class="ncf-chip" data-ncf-tratativa="todas">Todas</button>
|
||||||
|
<button type="button" class="ncf-chip is-active" data-ncf-tratativa="pendente">Pendentes</button>
|
||||||
|
<button type="button" class="ncf-chip" data-ncf-tratativa="tratado">Tratadas</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="pa-table-wrap">
|
<div class="pa-table-wrap">
|
||||||
<table class="pa-table">
|
<table class="pa-table ncf-table-sortable" id="ncf-table-sem-analise">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Código</th>
|
<th data-sort="codigo">Código <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Emissão</th>
|
<th data-sort="data_emissao">Emissão <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Área</th>
|
<th data-sort="area">Área <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Emissor</th>
|
<th data-sort="emissor_relato">Emissor <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Indicado para análise</th>
|
<th data-sort="indicado_analise">Indicado para análise <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Tratativa</th>
|
<th data-sort="status_tratativa">Tratativa <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -435,17 +443,25 @@
|
|||||||
<span class="ncf-count" id="ncf-analise-sem-acao-count"></span>
|
<span class="ncf-count" id="ncf-analise-sem-acao-count"></span>
|
||||||
</div>
|
</div>
|
||||||
<p class="ncf-section__hint">Análise já registrada, mas nenhuma ação foi aberta ainda.</p>
|
<p class="ncf-section__hint">Análise já registrada, mas nenhuma ação foi aberta ainda.</p>
|
||||||
|
<div class="ncf-filtro-grupo">
|
||||||
|
<span class="ncf-filtro-grupo__label">Tratativa:</span>
|
||||||
|
<div class="ncf-chip-row" id="ncf-filtro-tratativa-analise-sem-acao">
|
||||||
|
<button type="button" class="ncf-chip" data-ncf-tratativa="todas">Todas</button>
|
||||||
|
<button type="button" class="ncf-chip is-active" data-ncf-tratativa="pendente">Pendentes</button>
|
||||||
|
<button type="button" class="ncf-chip" data-ncf-tratativa="tratado">Tratadas</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="pa-table-wrap">
|
<div class="pa-table-wrap">
|
||||||
<table class="pa-table">
|
<table class="pa-table ncf-table-sortable" id="ncf-table-analise-sem-acao">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Código</th>
|
<th data-sort="codigo">Código <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Emissão</th>
|
<th data-sort="data_emissao">Emissão <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Área</th>
|
<th data-sort="area">Área <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Responsável pela análise</th>
|
<th data-sort="responsavel_analise">Responsável pela análise <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Descrição da análise</th>
|
<th data-sort="descricao_analise">Descrição da análise <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Tratativa</th>
|
<th data-sort="status_tratativa">Tratativa <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -463,7 +479,8 @@
|
|||||||
<div class="ncf-filtro-grupo">
|
<div class="ncf-filtro-grupo">
|
||||||
<span class="ncf-filtro-grupo__label">Prazo:</span>
|
<span class="ncf-filtro-grupo__label">Prazo:</span>
|
||||||
<div class="ncf-chip-row" id="ncf-filtro-prazo">
|
<div class="ncf-chip-row" id="ncf-filtro-prazo">
|
||||||
<button type="button" class="ncf-chip is-active" data-ncf-prazo="todas">Todas</button>
|
<button type="button" class="ncf-chip" data-ncf-prazo="todas">Todas</button>
|
||||||
|
<button type="button" class="ncf-chip is-active" data-ncf-prazo="nao_finalizada">Em aberto</button>
|
||||||
<button type="button" class="ncf-chip" data-ncf-prazo="vencida">Vencidas</button>
|
<button type="button" class="ncf-chip" data-ncf-prazo="vencida">Vencidas</button>
|
||||||
<button type="button" class="ncf-chip" data-ncf-prazo="vence_7_dias">Vence em 7 dias</button>
|
<button type="button" class="ncf-chip" data-ncf-prazo="vence_7_dias">Vence em 7 dias</button>
|
||||||
<button type="button" class="ncf-chip" data-ncf-prazo="vence_30_dias">Vence em 30 dias</button>
|
<button type="button" class="ncf-chip" data-ncf-prazo="vence_30_dias">Vence em 30 dias</button>
|
||||||
@ -481,18 +498,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pa-table-wrap">
|
<div class="pa-table-wrap">
|
||||||
<table class="pa-table">
|
<table class="pa-table ncf-table-sortable" id="ncf-table-acoes">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Ocorrência/Ação</th>
|
<th data-sort="ocorrencia_codigo">Ocorrência/Ação <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Área</th>
|
<th data-sort="ocorrencia_area">Área <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Tipo</th>
|
<th data-sort="tipo_acao">Tipo <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th>Ação</th>
|
<th class="ncf-th-filterable" data-sort="ocorrencia_tipo_ocorrencia">
|
||||||
<th>Responsável</th>
|
<span class="ncf-th-sort-label">Tipo de Ocorrência <span class="ncf-sort-icon">↕</span></span>
|
||||||
<th>Vencimento</th>
|
<button type="button" class="ncf-col-filtro-btn" data-ncf-col-filtro="tipo_ocorrencia" aria-label="Filtrar por tipo de ocorrência" aria-expanded="false">
|
||||||
<th>Prazo</th>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 5h16l-6 8v5l-4 2v-7z" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
<th>Tratativa</th>
|
</button>
|
||||||
|
<div class="ncf-col-filtro-popover" data-ncf-col-filtro-popover="tipo_ocorrencia" hidden>
|
||||||
|
<div class="checklist-box" id="ncf-col-filtro-tipo-lista"></div>
|
||||||
|
<p class="checklist-empty" id="ncf-col-filtro-tipo-empty" hidden>Nenhum tipo carregado.</p>
|
||||||
|
<div class="ncf-col-filtro-popover__actions">
|
||||||
|
<button type="button" class="btn-outline" data-ncf-col-filtro-limpar="tipo_ocorrencia">Limpar</button>
|
||||||
|
<button type="button" class="btn-solid" data-ncf-col-filtro-aplicar="tipo_ocorrencia">Aplicar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th data-sort="executor">Responsável <span class="ncf-sort-icon">↕</span></th>
|
||||||
|
<th class="ncf-th-filterable" data-sort="vencimento_efetivo">
|
||||||
|
<span class="ncf-th-sort-label">Vencimento <span class="ncf-sort-icon">↕</span></span>
|
||||||
|
<button type="button" class="ncf-col-filtro-btn" data-ncf-col-filtro="vencimento" aria-label="Filtrar por período de vencimento" aria-expanded="false">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 5h16l-6 8v5l-4 2v-7z" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<div class="ncf-col-filtro-popover" data-ncf-col-filtro-popover="vencimento" hidden>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="ncf-col-filtro-vencimento-de">De</label>
|
||||||
|
<input type="date" id="ncf-col-filtro-vencimento-de" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="ncf-col-filtro-vencimento-ate">Até</label>
|
||||||
|
<input type="date" id="ncf-col-filtro-vencimento-ate" />
|
||||||
|
</div>
|
||||||
|
<div class="ncf-col-filtro-popover__actions">
|
||||||
|
<button type="button" class="btn-outline" data-ncf-col-filtro-limpar="vencimento">Limpar</button>
|
||||||
|
<button type="button" class="btn-solid" data-ncf-col-filtro-aplicar="vencimento">Aplicar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th data-sort="ultimo_acompanhamento_em">Último Acompanhamento <span class="ncf-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="status_prazo">Prazo <span class="ncf-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="status_tratativa">Tratativa <span class="ncf-sort-icon">↕</span></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user