Merge branch 'main' of https://git.depaula.app.br/Gabriel/portal_publico
This commit is contained in:
commit
0f9358079e
File diff suppressed because it is too large
Load Diff
124881
media/nao_conformidades/acoes/acoes_08092026_-_com_finalizadas.xls
Normal file
124881
media/nao_conformidades/acoes/acoes_08092026_-_com_finalizadas.xls
Normal file
File diff suppressed because it is too large
Load Diff
BIN
media/nao_conformidades/ocorrencias/ocorrencias_pa_08092026.xlsx
Normal file
BIN
media/nao_conformidades/ocorrencias/ocorrencias_pa_08092026.xlsx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 6.0.7 on 2026-09-08 19:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('portal_api', '0054_importacaoplanosaudedepaula_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ncocorrencia',
|
||||||
|
name='data_finalizacao',
|
||||||
|
field=models.DateField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ncocorrencia',
|
||||||
|
name='fase',
|
||||||
|
field=models.CharField(blank=True, max_length=100, verbose_name='Fase (Sigsistem)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1824,6 +1824,16 @@ class NCOcorrencia(models.Model):
|
|||||||
# `acoes.count()` porque uma ocorrência pode ter linhas mistas.
|
# `acoes.count()` porque uma ocorrência pode ter linhas mistas.
|
||||||
analise_sem_acao_detectada = models.BooleanField(default=False)
|
analise_sem_acao_detectada = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
# Só relevantes/preenchidos quando a ocorrência não tem nenhuma ação
|
||||||
|
# (linha do export sem "Código da Ação") e o Sigsistem fecha a própria
|
||||||
|
# ocorrência direto — ex.: tipo "Elogios de Clientes", que não exige
|
||||||
|
# análise/ação nenhuma. Vêm das MESMAS colunas "Data de Finalização"/
|
||||||
|
# "Fase" que, numa linha COM ação, descrevem a ação (ver
|
||||||
|
# NCAcao.data_finalizacao/fase) — o Sigsistem reaproveita as colunas
|
||||||
|
# conforme o que a linha representa; ver leiaute_ocorrencias.py.
|
||||||
|
data_finalizacao = models.DateField(null=True, blank=True)
|
||||||
|
fase = models.CharField("Fase (Sigsistem)", max_length=100, blank=True)
|
||||||
|
|
||||||
status_tratativa = models.CharField(
|
status_tratativa = models.CharField(
|
||||||
"Status de tratativa", max_length=10, choices=STATUS_CHOICES, default=STATUS_PENDENTE
|
"Status de tratativa", max_length=10, choices=STATUS_CHOICES, default=STATUS_PENDENTE
|
||||||
)
|
)
|
||||||
|
|||||||
@ -35,6 +35,11 @@ STATUS_PRAZO_LABELS = {
|
|||||||
STATUS_SEM_VENCIMENTO: "Sem vencimento",
|
STATUS_SEM_VENCIMENTO: "Sem vencimento",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Valor de "Fase" (coluna do Sigsistem, texto livre) que indica que o item
|
||||||
|
# está fechado — usado tanto pra NCAcao.fase quanto pra NCOcorrencia.fase
|
||||||
|
# (ocorrência fechada sem precisar de ação, ex.: "Elogios de Clientes").
|
||||||
|
FASE_FINALIZADA = "Finalizada"
|
||||||
|
|
||||||
CATEGORIA_CORRETIVA = "Ação Corretiva"
|
CATEGORIA_CORRETIVA = "Ação Corretiva"
|
||||||
CATEGORIA_CORRECAO = "Correção"
|
CATEGORIA_CORRECAO = "Correção"
|
||||||
CATEGORIA_OUTROS = "Outros"
|
CATEGORIA_OUTROS = "Outros"
|
||||||
|
|||||||
@ -179,6 +179,17 @@ def parse_ocorrencias(caminho: str) -> tuple[list[OcorrenciaExtraida], list[str]
|
|||||||
# preenchida, é o caso "Análise sem Ação" (regra #8 da skill).
|
# preenchida, é o caso "Análise sem Ação" (regra #8 da skill).
|
||||||
if _texto(campo(linha, "Descrição da Análise da Ocorrência")):
|
if _texto(campo(linha, "Descrição da Análise da Ocorrência")):
|
||||||
ocorrencia.tem_linha_analise_sem_acao = True
|
ocorrencia.tem_linha_analise_sem_acao = True
|
||||||
|
# Sem ação, "Data de Finalização"/"Fase" descrevem o
|
||||||
|
# fechamento da própria ocorrência (não de uma ação) — ex.:
|
||||||
|
# "Elogios de Clientes", que o Sigsistem finaliza direto sem
|
||||||
|
# exigir análise/ação nenhuma (bug real: antes disso, essas
|
||||||
|
# ocorrências ficavam "Sem Análise" pra sempre no Portal).
|
||||||
|
data_fin = _parse_data(campo(linha, "Data de Finalização"))
|
||||||
|
if data_fin:
|
||||||
|
ocorrencia.data_finalizacao = data_fin
|
||||||
|
fase_linha = _texto(campo(linha, "Fase"))
|
||||||
|
if fase_linha:
|
||||||
|
ocorrencia.fase = fase_linha
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ocorrencia.acoes.append(
|
ocorrencia.acoes.append(
|
||||||
|
|||||||
@ -76,6 +76,11 @@ class OcorrenciaExtraida:
|
|||||||
# original) — checado por linha durante o parsing, não pelo agregado
|
# original) — checado por linha durante o parsing, não pelo agregado
|
||||||
# `descricao_analise` (que só reflete a primeira linha da ocorrência).
|
# `descricao_analise` (que só reflete a primeira linha da ocorrência).
|
||||||
tem_linha_analise_sem_acao: bool = False
|
tem_linha_analise_sem_acao: bool = False
|
||||||
|
# Vêm de uma linha SEM ação onde o Sigsistem fecha a própria ocorrência
|
||||||
|
# direto (ex.: "Elogios de Clientes") — ver NCOcorrencia.data_finalizacao/
|
||||||
|
# fase em models.py pro porquê de reaproveitar essas colunas.
|
||||||
|
data_finalizacao: date | None = None
|
||||||
|
fase: str = ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sem_analise(self) -> bool:
|
def sem_analise(self) -> bool:
|
||||||
|
|||||||
@ -2654,6 +2654,8 @@ def _campos_nc_ocorrencia(ocorrencia_extraida: Any) -> dict[str, Any]:
|
|||||||
"responsavel_analise": ocorrencia_extraida.responsavel_analise,
|
"responsavel_analise": ocorrencia_extraida.responsavel_analise,
|
||||||
"data_analise": ocorrencia_extraida.data_analise,
|
"data_analise": ocorrencia_extraida.data_analise,
|
||||||
"analise_sem_acao_detectada": ocorrencia_extraida.tem_linha_analise_sem_acao,
|
"analise_sem_acao_detectada": ocorrencia_extraida.tem_linha_analise_sem_acao,
|
||||||
|
"data_finalizacao": ocorrencia_extraida.data_finalizacao,
|
||||||
|
"fase": ocorrencia_extraida.fase,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2976,9 +2978,34 @@ class NCOcorrenciaViewSet(viewsets.ModelViewSet):
|
|||||||
if params.get("status_tratativa"):
|
if params.get("status_tratativa"):
|
||||||
queryset = queryset.filter(status_tratativa=params["status_tratativa"])
|
queryset = queryset.filter(status_tratativa=params["status_tratativa"])
|
||||||
if params.get("sem_analise") == "true":
|
if params.get("sem_analise") == "true":
|
||||||
queryset = queryset.filter(descricao_analise="")
|
# Exclui ocorrência que o Sigsistem já fechou sem exigir análise
|
||||||
|
# (ex.: "Elogios de Clientes") — bug real: sem isso, essas
|
||||||
|
# ficavam "Sem Análise" pra sempre, exigindo "Marcar como
|
||||||
|
# tratado" manual pra algo que já está resolvido no Sigsistem.
|
||||||
|
# Duas formas de "já fechada" (a mesma ocorrência real já
|
||||||
|
# apareceu nas duas): (a) não tem nenhuma ação — nesse caso quem
|
||||||
|
# carrega "Fase" é a própria ocorrência (ver
|
||||||
|
# nao_conformidades/leiaute_ocorrencias.py); (b) tem ação(ões), e
|
||||||
|
# TODAS elas já têm `data_finalizacao` preenchida — nesse caso é
|
||||||
|
# a ação que carrega a finalização, não a ocorrência.
|
||||||
|
queryset = (
|
||||||
|
queryset.filter(descricao_analise="")
|
||||||
|
.annotate(
|
||||||
|
total_acoes=Count("acoes", distinct=True),
|
||||||
|
acoes_abertas=Count("acoes", filter=Q(acoes__data_finalizacao__isnull=True), distinct=True),
|
||||||
|
)
|
||||||
|
.exclude(Q(fase=nc_classificacao.FASE_FINALIZADA) | Q(total_acoes__gt=0, acoes_abertas=0))
|
||||||
|
)
|
||||||
if params.get("analise_sem_acao") == "true":
|
if params.get("analise_sem_acao") == "true":
|
||||||
queryset = queryset.filter(analise_sem_acao_detectada=True)
|
# Exclui ocorrência já finalizada no Sigsistem sem nunca ter tido
|
||||||
|
# uma ação (decisão explícita do usuário, mesmo sabendo que boa
|
||||||
|
# parte são NC/Reclamação de Cliente reais sem Ação Corretiva
|
||||||
|
# formal — confia que o indicador "NCs sem ação corretiva" do
|
||||||
|
# dashboard, que não filtra por finalizada, continua cobrindo
|
||||||
|
# esses casos pra quem olha o Dashboard).
|
||||||
|
queryset = queryset.filter(analise_sem_acao_detectada=True).exclude(
|
||||||
|
fase=nc_classificacao.FASE_FINALIZADA
|
||||||
|
)
|
||||||
if params.get("search"):
|
if params.get("search"):
|
||||||
termo = params["search"]
|
termo = params["search"]
|
||||||
queryset = queryset.filter(Q(assunto__icontains=termo) | Q(codigo__icontains=termo))
|
queryset = queryset.filter(Q(assunto__icontains=termo) | Q(codigo__icontains=termo))
|
||||||
@ -3009,10 +3036,13 @@ class NCOcorrenciaViewSet(viewsets.ModelViewSet):
|
|||||||
return Response(NCOcorrenciaSerializer(ocorrencia).data)
|
return Response(NCOcorrenciaSerializer(ocorrencia).data)
|
||||||
|
|
||||||
|
|
||||||
def _filtra_status_prazo(queryset: QuerySet[NCAcao], bucket: str, hoje: date) -> QuerySet[NCAcao]:
|
def _status_prazo_q(bucket: str, hoje: date) -> Q:
|
||||||
"""Replica os mesmos cortes de `nao_conformidades.classificacao.status_prazo()`
|
"""Devolve a condição de UM bucket como `Q`, pra poder combinar vários
|
||||||
como filtros de data sobre `vencimento_efetivo`, pra poder filtrar no banco
|
buckets com OR (`_filtra_status_prazo` abaixo) — o frontend permite
|
||||||
(o cálculo em si roda em Python, não dá pra usar direto num `.filter()`).
|
marcar mais de um ao mesmo tempo (ex.: "Vencidas" + "Vence em 7 dias").
|
||||||
|
Replica os mesmos cortes de `nao_conformidades.classificacao.status_prazo()`
|
||||||
|
como filtros de data sobre `vencimento_efetivo` (o cálculo em si roda em
|
||||||
|
Python, não dá pra usar direto num `.filter()`).
|
||||||
|
|
||||||
Todo bucket de prazo (vencida/vence em X dias/no prazo/sem vencimento)
|
Todo bucket de prazo (vencida/vence em X dias/no prazo/sem vencimento)
|
||||||
exclui ações já finalizadas — bug real corrigido: um export com
|
exclui ações já finalizadas — bug real corrigido: um export com
|
||||||
@ -3021,26 +3051,35 @@ def _filtra_status_prazo(queryset: QuerySet[NCAcao], bucket: str, hoje: date) ->
|
|||||||
esmagadora maioria aparecer como "vencida" sem estar realmente em
|
esmagadora maioria aparecer como "vencida" sem estar realmente em
|
||||||
aberto."""
|
aberto."""
|
||||||
if bucket == nc_classificacao.STATUS_FINALIZADA:
|
if bucket == nc_classificacao.STATUS_FINALIZADA:
|
||||||
return queryset.filter(data_finalizacao__isnull=False)
|
return Q(data_finalizacao__isnull=False)
|
||||||
|
|
||||||
queryset = queryset.filter(data_finalizacao__isnull=True)
|
aberta = Q(data_finalizacao__isnull=True)
|
||||||
if bucket == nc_classificacao.STATUS_NAO_FINALIZADA:
|
if bucket == nc_classificacao.STATUS_NAO_FINALIZADA:
|
||||||
return queryset
|
return aberta
|
||||||
if bucket == nc_classificacao.STATUS_VENCIDA:
|
if bucket == nc_classificacao.STATUS_VENCIDA:
|
||||||
return queryset.filter(vencimento_efetivo__lt=hoje)
|
return aberta & Q(vencimento_efetivo__lt=hoje)
|
||||||
if bucket == nc_classificacao.STATUS_VENCE_7:
|
if bucket == nc_classificacao.STATUS_VENCE_7:
|
||||||
return queryset.filter(vencimento_efetivo__gte=hoje, vencimento_efetivo__lte=hoje + timedelta(days=7))
|
return aberta & Q(vencimento_efetivo__gte=hoje, vencimento_efetivo__lte=hoje + timedelta(days=7))
|
||||||
if bucket == nc_classificacao.STATUS_VENCE_30:
|
if bucket == nc_classificacao.STATUS_VENCE_30:
|
||||||
return queryset.filter(
|
return aberta & Q(
|
||||||
vencimento_efetivo__gt=hoje + timedelta(days=7), vencimento_efetivo__lte=hoje + timedelta(days=30)
|
vencimento_efetivo__gt=hoje + timedelta(days=7), vencimento_efetivo__lte=hoje + timedelta(days=30)
|
||||||
)
|
)
|
||||||
if bucket == nc_classificacao.STATUS_NO_PRAZO:
|
if bucket == nc_classificacao.STATUS_NO_PRAZO:
|
||||||
return queryset.filter(vencimento_efetivo__gt=hoje + timedelta(days=30))
|
return aberta & Q(vencimento_efetivo__gt=hoje + timedelta(days=30))
|
||||||
if bucket == nc_classificacao.STATUS_SEM_VENCIMENTO:
|
if bucket == nc_classificacao.STATUS_SEM_VENCIMENTO:
|
||||||
return queryset.filter(vencimento_efetivo__isnull=True)
|
return aberta & Q(vencimento_efetivo__isnull=True)
|
||||||
raise ValidationError({"status_prazo": "Valor inválido."})
|
raise ValidationError({"status_prazo": "Valor inválido."})
|
||||||
|
|
||||||
|
|
||||||
|
def _filtra_status_prazo(queryset: QuerySet[NCAcao], buckets: list[str], hoje: date) -> QuerySet[NCAcao]:
|
||||||
|
"""Combina um ou mais buckets de prazo com OR (filtro múltiplo do
|
||||||
|
frontend — ver `?status_prazo=` repetido em `NCAcaoViewSet.get_queryset`)."""
|
||||||
|
combinado = Q()
|
||||||
|
for bucket in buckets:
|
||||||
|
combinado |= _status_prazo_q(bucket, hoje)
|
||||||
|
return queryset.filter(combinado)
|
||||||
|
|
||||||
|
|
||||||
class NCAcaoViewSet(viewsets.ModelViewSet):
|
class NCAcaoViewSet(viewsets.ModelViewSet):
|
||||||
"""Ações persistidas — só leitura + tratativa (mesmo espírito de
|
"""Ações persistidas — só leitura + tratativa (mesmo espírito de
|
||||||
NCOcorrenciaViewSet)."""
|
NCOcorrenciaViewSet)."""
|
||||||
@ -3061,9 +3100,9 @@ class NCAcaoViewSet(viewsets.ModelViewSet):
|
|||||||
params = self.request.query_params
|
params = self.request.query_params
|
||||||
if params.get("status_tratativa"):
|
if params.get("status_tratativa"):
|
||||||
queryset = queryset.filter(status_tratativa=params["status_tratativa"])
|
queryset = queryset.filter(status_tratativa=params["status_tratativa"])
|
||||||
status_prazo = params.get("status_prazo")
|
status_prazo_list = params.getlist("status_prazo")
|
||||||
if status_prazo:
|
if status_prazo_list:
|
||||||
queryset = _filtra_status_prazo(queryset, status_prazo, timezone.localdate())
|
queryset = _filtra_status_prazo(queryset, status_prazo_list, timezone.localdate())
|
||||||
if params.get("search"):
|
if params.get("search"):
|
||||||
termo = params["search"]
|
termo = params["search"]
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
|
|||||||
@ -256,6 +256,73 @@
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Resumo copiável (canto superior direito da Gestão) — mesmo mecanismo de
|
||||||
|
popover position:fixed do filtro de coluna acima (ver
|
||||||
|
pidNcfFecharPopoversColuna() em nao-conformidades.js, que fecha os dois
|
||||||
|
tipos de popover de um jeito só). */
|
||||||
|
.ncf-resumo-wrap {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-popover {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 30;
|
||||||
|
width: 320px;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: var(--space-4);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-popover__preview {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-titulo {
|
||||||
|
font-weight: 700;
|
||||||
|
margin: var(--space-3) 0 var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-titulo:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-linha {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-linha--vencida {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-linha--vence7 {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-vazio {
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-resumo-popover__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
/* Dashboard */
|
/* Dashboard */
|
||||||
.ncf-dashboard-toolbar {
|
.ncf-dashboard-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -501,12 +568,22 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Busca compartilhada da Gestão */
|
/* Busca compartilhada da Gestão + Resumo copiável (canto superior direito) */
|
||||||
.ncf-gestao-toolbar {
|
.ncf-gestao-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-3);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncf-gestao-toolbar__busca {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
margin-bottom: var(--space-5);
|
flex: 1;
|
||||||
|
min-width: 260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ncf-gestao-search {
|
.ncf-gestao-search {
|
||||||
|
|||||||
@ -215,10 +215,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
let semAnaliseTodos = [];
|
let semAnaliseTodos = [];
|
||||||
let analiseSemAcaoTodos = [];
|
let analiseSemAcaoTodos = [];
|
||||||
let acoesTodos = [];
|
let acoesTodos = [];
|
||||||
// "nao_finalizada" (não "todas") é o padrão — a tela deve abrir só com o
|
// Filtro de Prazo é múltiplo (pedido explícito do usuário — poder ver
|
||||||
// que ainda está em aberto (data_finalizacao vazia no Sigsistem), pedido
|
// "Vencidas" e "Vence em 7 dias" ao mesmo tempo, por exemplo): cada chip
|
||||||
// explícito do usuário; "Todas" continua disponível como chip separado.
|
// liga/desliga independente, combinados com OR no servidor (ver
|
||||||
let prazoFiltroAtual = "nao_finalizada";
|
// `_filtra_status_prazo` em views.py). Todos os buckets em aberto vêm
|
||||||
|
// marcados por padrão — só "Finalizadas" começa desmarcado.
|
||||||
|
const PID_NCF_PRAZO_BUCKETS_ABERTO = ["vencida", "vence_7_dias", "vence_30_dias", "no_prazo", "sem_vencimento"];
|
||||||
|
let prazoFiltrosSelecionados = new Set(PID_NCF_PRAZO_BUCKETS_ABERTO);
|
||||||
let tratativaFiltroAtual = "pendente";
|
let tratativaFiltroAtual = "pendente";
|
||||||
let semAnaliseTratativaFiltroAtual = "pendente";
|
let semAnaliseTratativaFiltroAtual = "pendente";
|
||||||
let analiseSemAcaoTratativaFiltroAtual = "pendente";
|
let analiseSemAcaoTratativaFiltroAtual = "pendente";
|
||||||
@ -242,6 +245,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
let vencimentoFiltroDe = "";
|
let vencimentoFiltroDe = "";
|
||||||
let vencimentoFiltroAte = "";
|
let vencimentoFiltroAte = "";
|
||||||
|
|
||||||
|
// Espelham exatamente o que está sendo mostrado em cada tabela (depois de
|
||||||
|
// busca/chips/filtro de coluna/ordenação) — alimentam o Resumo copiável
|
||||||
|
// (ver atualizarResumoGestao()), atualizados no fim de cada render*().
|
||||||
|
let acoesItensVisiveis = [];
|
||||||
|
let semAnaliseItensVisiveis = [];
|
||||||
|
let analiseSemAcaoItensVisiveis = [];
|
||||||
|
|
||||||
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");
|
||||||
@ -279,11 +289,24 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
Array.from(container.children).forEach((btn) => btn.classList.toggle("is-active", btn.dataset[datasetAttr] === valor));
|
Array.from(container.children).forEach((btn) => btn.classList.toggle("is-active", btn.dataset[datasetAttr] === valor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mesma ideia de atualizarChipsAtivos(), mas pra um filtro de múltipla
|
||||||
|
// escolha (Prazo de Ações) — ativo se o valor do chip estiver no Set.
|
||||||
|
function atualizarChipsMultiAtivos(container, datasetAttr, selecionados) {
|
||||||
|
Array.from(container.children).forEach((btn) =>
|
||||||
|
btn.classList.toggle("is-active", selecionados.has(btn.dataset[datasetAttr]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
filtroPrazoEl.addEventListener("click", (evento) => {
|
filtroPrazoEl.addEventListener("click", (evento) => {
|
||||||
const btn = evento.target.closest("[data-ncf-prazo]");
|
const btn = evento.target.closest("[data-ncf-prazo]");
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
prazoFiltroAtual = btn.dataset.ncfPrazo;
|
const valor = btn.dataset.ncfPrazo;
|
||||||
atualizarChipsAtivos(filtroPrazoEl, "ncfPrazo", prazoFiltroAtual);
|
if (prazoFiltrosSelecionados.has(valor)) {
|
||||||
|
prazoFiltrosSelecionados.delete(valor);
|
||||||
|
} else {
|
||||||
|
prazoFiltrosSelecionados.add(valor);
|
||||||
|
}
|
||||||
|
atualizarChipsMultiAtivos(filtroPrazoEl, "ncfPrazo", prazoFiltrosSelecionados);
|
||||||
carregarAcoes();
|
carregarAcoes();
|
||||||
});
|
});
|
||||||
filtroTratativaEl.addEventListener("click", (evento) => {
|
filtroTratativaEl.addEventListener("click", (evento) => {
|
||||||
@ -353,6 +376,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
]);
|
]);
|
||||||
itens = pidNcfAplicarSort(itens, semAnaliseSortKey, semAnaliseSortDir, PID_NCF_SEM_ANALISE_SORT_VALOR);
|
itens = pidNcfAplicarSort(itens, semAnaliseSortKey, semAnaliseSortDir, PID_NCF_SEM_ANALISE_SORT_VALOR);
|
||||||
pidNcfAtualizarSortHeaders(semAnaliseSortHeaders, semAnaliseSortKey, semAnaliseSortDir);
|
pidNcfAtualizarSortHeaders(semAnaliseSortHeaders, semAnaliseSortKey, semAnaliseSortDir);
|
||||||
|
semAnaliseItensVisiveis = itens;
|
||||||
|
atualizarResumoGestao();
|
||||||
pidNcfAtualizarContador(semAnaliseCount, itens.length);
|
pidNcfAtualizarContador(semAnaliseCount, itens.length);
|
||||||
semAnaliseBody.innerHTML = "";
|
semAnaliseBody.innerHTML = "";
|
||||||
semAnaliseEmpty.hidden = itens.length > 0;
|
semAnaliseEmpty.hidden = itens.length > 0;
|
||||||
@ -429,6 +454,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
]);
|
]);
|
||||||
itens = pidNcfAplicarSort(itens, analiseSemAcaoSortKey, analiseSemAcaoSortDir, PID_NCF_ANALISE_SEM_ACAO_SORT_VALOR);
|
itens = pidNcfAplicarSort(itens, analiseSemAcaoSortKey, analiseSemAcaoSortDir, PID_NCF_ANALISE_SEM_ACAO_SORT_VALOR);
|
||||||
pidNcfAtualizarSortHeaders(analiseSemAcaoSortHeaders, analiseSemAcaoSortKey, analiseSemAcaoSortDir);
|
pidNcfAtualizarSortHeaders(analiseSemAcaoSortHeaders, analiseSemAcaoSortKey, analiseSemAcaoSortDir);
|
||||||
|
analiseSemAcaoItensVisiveis = itens;
|
||||||
|
atualizarResumoGestao();
|
||||||
pidNcfAtualizarContador(analiseSemAcaoCount, itens.length);
|
pidNcfAtualizarContador(analiseSemAcaoCount, itens.length);
|
||||||
analiseSemAcaoBody.innerHTML = "";
|
analiseSemAcaoBody.innerHTML = "";
|
||||||
analiseSemAcaoEmpty.hidden = itens.length > 0;
|
analiseSemAcaoEmpty.hidden = itens.length > 0;
|
||||||
@ -546,8 +573,18 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
async function carregarAcoes() {
|
async function carregarAcoes() {
|
||||||
|
if (prazoFiltrosSelecionados.size === 0) {
|
||||||
|
// Nenhum chip de Prazo marcado — mesmo espírito de um checklist todo
|
||||||
|
// desmarcado, não mostra nada (em vez de interpretar como "sem
|
||||||
|
// filtro" e trazer tudo, o que seria surpreendente pro usuário).
|
||||||
|
acoesTodos = [];
|
||||||
|
tipoOcorrenciaOpcoesDisponiveis = [];
|
||||||
|
renderTipoOcorrenciaChecklist();
|
||||||
|
renderAcoes();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (prazoFiltroAtual !== "todas") params.set("status_prazo", prazoFiltroAtual);
|
prazoFiltrosSelecionados.forEach((bucket) => params.append("status_prazo", bucket));
|
||||||
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 : ""}`);
|
||||||
@ -582,6 +619,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
itens = pidNcfFiltraColunaAcoes(itens);
|
itens = pidNcfFiltraColunaAcoes(itens);
|
||||||
itens = pidNcfAplicarSort(itens, acoesSortKey, acoesSortDir, PID_NCF_ACOES_SORT_VALOR);
|
itens = pidNcfAplicarSort(itens, acoesSortKey, acoesSortDir, PID_NCF_ACOES_SORT_VALOR);
|
||||||
pidNcfAtualizarSortHeaders(acoesSortHeaders, acoesSortKey, acoesSortDir);
|
pidNcfAtualizarSortHeaders(acoesSortHeaders, acoesSortKey, acoesSortDir);
|
||||||
|
acoesItensVisiveis = itens;
|
||||||
|
atualizarResumoGestao();
|
||||||
pidNcfAtualizarContador(acoesCount, itens.length);
|
pidNcfAtualizarContador(acoesCount, itens.length);
|
||||||
acoesBody.innerHTML = "";
|
acoesBody.innerHTML = "";
|
||||||
acoesEmpty.hidden = itens.length > 0;
|
acoesEmpty.hidden = itens.length > 0;
|
||||||
@ -748,6 +787,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
function pidNcfFecharPopoversColuna() {
|
function pidNcfFecharPopoversColuna() {
|
||||||
acoesTableEl.querySelectorAll(".ncf-col-filtro-popover").forEach((p) => (p.hidden = true));
|
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.querySelectorAll("[data-ncf-col-filtro]").forEach((b) => b.setAttribute("aria-expanded", "false"));
|
||||||
|
// Também fecha o popover do Resumo (ver seção própria mais abaixo) —
|
||||||
|
// reaproveitado aqui pra não duplicar a lógica de "só um popover aberto
|
||||||
|
// por vez" em dois lugares.
|
||||||
|
if (resumoPopover && !resumoPopover.hidden) {
|
||||||
|
resumoPopover.hidden = true;
|
||||||
|
resumoBtn.setAttribute("aria-expanded", "false");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acoesTableEl.addEventListener("click", (evento) => {
|
acoesTableEl.addEventListener("click", (evento) => {
|
||||||
@ -807,7 +853,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("click", (evento) => {
|
document.addEventListener("click", (evento) => {
|
||||||
if (evento.target.closest(".ncf-th-filterable")) return;
|
if (evento.target.closest(".ncf-th-filterable") || evento.target.closest(".ncf-resumo-wrap")) return;
|
||||||
pidNcfFecharPopoversColuna();
|
pidNcfFecharPopoversColuna();
|
||||||
});
|
});
|
||||||
document.addEventListener("keydown", (evento) => {
|
document.addEventListener("keydown", (evento) => {
|
||||||
@ -819,6 +865,96 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
window.addEventListener("scroll", () => pidNcfFecharPopoversColuna(), true);
|
window.addEventListener("scroll", () => pidNcfFecharPopoversColuna(), true);
|
||||||
window.addEventListener("resize", () => pidNcfFecharPopoversColuna());
|
window.addEventListener("resize", () => pidNcfFecharPopoversColuna());
|
||||||
|
|
||||||
|
// ---------------- Resumo copiável (Ações em aberto / Sem análise / Análise sem ação) ----------------
|
||||||
|
// Reflete exatamente o que está visível em cada uma das 3 tabelas da
|
||||||
|
// Gestão no momento (depois de busca/chips/filtro de coluna/ordenação) —
|
||||||
|
// ver os `*ItensVisiveis` atualizados no fim de cada render*() acima.
|
||||||
|
const resumoBtn = document.getElementById("ncf-resumo-btn");
|
||||||
|
const resumoPopover = document.getElementById("ncf-resumo-popover");
|
||||||
|
const resumoPreview = document.getElementById("ncf-resumo-preview");
|
||||||
|
const resumoCount = document.getElementById("ncf-resumo-count");
|
||||||
|
const resumoCopiarBtn = document.getElementById("ncf-resumo-copiar-btn");
|
||||||
|
let resumoTextoAtual = "";
|
||||||
|
|
||||||
|
function pidNcfDescricaoLinhaAcao(acaoItem) {
|
||||||
|
const data = pidNcfFormatDate(acaoItem.vencimento_efetivo);
|
||||||
|
const codigo = `${acaoItem.ocorrencia_codigo}/${acaoItem.codigo}`;
|
||||||
|
if (acaoItem.status_prazo === "vencida") return { texto: `${codigo} - Vencida em ${data}`, cor: "vencida" };
|
||||||
|
if (acaoItem.status_prazo === "vence_7_dias") return { texto: `${codigo} - Vencerá em ${data}`, cor: "vence7" };
|
||||||
|
if (acaoItem.status_prazo === "sem_vencimento") return { texto: `${codigo} - Sem vencimento definido`, cor: "normal" };
|
||||||
|
if (acaoItem.status_prazo === "finalizada") return { texto: `${codigo} - Finalizada em ${data}`, cor: "normal" };
|
||||||
|
return { texto: `${codigo} - Vencerá em ${data}`, cor: "normal" }; // vence_30_dias / no_prazo
|
||||||
|
}
|
||||||
|
|
||||||
|
function pidNcfDescricaoLinhaOcorrencia(oc) {
|
||||||
|
return { texto: `nº ${oc.codigo} - emitida em ${pidNcfFormatDate(oc.data_emissao)}`, cor: "normal" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function montarBlocosResumo() {
|
||||||
|
const blocos = [];
|
||||||
|
if (acoesItensVisiveis.length) {
|
||||||
|
blocos.push({ titulo: "Ações em aberto:", linhas: acoesItensVisiveis.map(pidNcfDescricaoLinhaAcao) });
|
||||||
|
}
|
||||||
|
if (semAnaliseItensVisiveis.length) {
|
||||||
|
blocos.push({ titulo: "Ocorrência sem análise:", linhas: semAnaliseItensVisiveis.map(pidNcfDescricaoLinhaOcorrencia) });
|
||||||
|
}
|
||||||
|
if (analiseSemAcaoItensVisiveis.length) {
|
||||||
|
blocos.push({ titulo: "Análise sem ação:", linhas: analiseSemAcaoItensVisiveis.map(pidNcfDescricaoLinhaOcorrencia) });
|
||||||
|
}
|
||||||
|
return blocos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pidNcfResumoTextoPlano(blocos) {
|
||||||
|
return blocos.map((bloco) => `${bloco.titulo}\n${bloco.linhas.map((l) => l.texto).join("\n")}`).join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function pidNcfResumoHtml(blocos) {
|
||||||
|
if (!blocos.length) return '<p class="ncf-resumo-vazio">Nada pendente com os filtros atuais.</p>';
|
||||||
|
return blocos
|
||||||
|
.map(
|
||||||
|
(bloco) => `
|
||||||
|
<p class="ncf-resumo-titulo">${pidNcfEscapeHtml(bloco.titulo)}</p>
|
||||||
|
${bloco.linhas
|
||||||
|
.map((l) => `<p class="ncf-resumo-linha ncf-resumo-linha--${l.cor}">${pidNcfEscapeHtml(l.texto)}</p>`)
|
||||||
|
.join("")}`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function atualizarResumoGestao() {
|
||||||
|
const blocos = montarBlocosResumo();
|
||||||
|
resumoTextoAtual = pidNcfResumoTextoPlano(blocos);
|
||||||
|
const totalLinhas = blocos.reduce((soma, bloco) => soma + bloco.linhas.length, 0);
|
||||||
|
pidNcfAtualizarContador(resumoCount, totalLinhas);
|
||||||
|
resumoPreview.innerHTML = pidNcfResumoHtml(blocos);
|
||||||
|
}
|
||||||
|
|
||||||
|
resumoBtn.addEventListener("click", () => {
|
||||||
|
const jaAberto = !resumoPopover.hidden;
|
||||||
|
pidNcfFecharPopoversColuna();
|
||||||
|
if (!jaAberto) {
|
||||||
|
const rect = resumoBtn.getBoundingClientRect();
|
||||||
|
const left = Math.min(rect.right - 320, window.innerWidth - 328);
|
||||||
|
resumoPopover.style.top = `${rect.bottom + 6}px`;
|
||||||
|
resumoPopover.style.left = `${Math.max(left, 8)}px`;
|
||||||
|
}
|
||||||
|
resumoPopover.hidden = jaAberto;
|
||||||
|
resumoBtn.setAttribute("aria-expanded", String(!jaAberto));
|
||||||
|
});
|
||||||
|
|
||||||
|
resumoCopiarBtn.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(resumoTextoAtual);
|
||||||
|
const textoOriginal = resumoCopiarBtn.textContent;
|
||||||
|
resumoCopiarBtn.textContent = "Copiado!";
|
||||||
|
setTimeout(() => {
|
||||||
|
resumoCopiarBtn.textContent = textoOriginal;
|
||||||
|
}, 1500);
|
||||||
|
} catch (e) {
|
||||||
|
await pidAlert("Não foi possível copiar o texto.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------- 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");
|
||||||
@ -837,7 +973,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
// Define o filtro ANTES de carregar — se a Gestão ainda não tinha sido
|
// Define o filtro ANTES de carregar — se a Gestão ainda não tinha sido
|
||||||
// aberta, o primeiro carregamento já sai com o filtro certo; se já
|
// aberta, o primeiro carregamento já sai com o filtro certo; se já
|
||||||
// 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;
|
prazoFiltrosSelecionados = new Set([bucket]);
|
||||||
tratativaFiltroAtual = "todas";
|
tratativaFiltroAtual = "todas";
|
||||||
// Zera os filtros de coluna (Tipo de Ocorrência/Vencimento) — é uma
|
// Zera os filtros de coluna (Tipo de Ocorrência/Vencimento) — é uma
|
||||||
// navegação "leve a tela pra este estado", não deveria carregar filtro
|
// navegação "leve a tela pra este estado", não deveria carregar filtro
|
||||||
@ -849,7 +985,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const vencimentoAteInput = document.getElementById("ncf-col-filtro-vencimento-ate");
|
const vencimentoAteInput = document.getElementById("ncf-col-filtro-vencimento-ate");
|
||||||
if (vencimentoDeInput) vencimentoDeInput.value = "";
|
if (vencimentoDeInput) vencimentoDeInput.value = "";
|
||||||
if (vencimentoAteInput) vencimentoAteInput.value = "";
|
if (vencimentoAteInput) vencimentoAteInput.value = "";
|
||||||
atualizarChipsAtivos(filtroPrazoEl, "ncfPrazo", bucket);
|
atualizarChipsMultiAtivos(filtroPrazoEl, "ncfPrazo", prazoFiltrosSelecionados);
|
||||||
atualizarChipsAtivos(filtroTratativaEl, "ncfTratativa", "todas");
|
atualizarChipsAtivos(filtroTratativaEl, "ncfTratativa", "todas");
|
||||||
ativarTab("gestao");
|
ativarTab("gestao");
|
||||||
if (gestaoCarregada) {
|
if (gestaoCarregada) {
|
||||||
|
|||||||
@ -399,9 +399,101 @@
|
|||||||
|
|
||||||
<div class="pa-tab-panel" data-ncf-panel="gestao" hidden>
|
<div class="pa-tab-panel" data-ncf-panel="gestao" hidden>
|
||||||
<div class="ncf-gestao-toolbar">
|
<div class="ncf-gestao-toolbar">
|
||||||
|
<div class="ncf-gestao-toolbar__busca">
|
||||||
<input type="search" id="ncf-gestao-search" class="ncf-gestao-search" placeholder="Buscar por colaborador, cliente/empresa ou assunto..." autocomplete="off" />
|
<input type="search" id="ncf-gestao-search" class="ncf-gestao-search" placeholder="Buscar por colaborador, cliente/empresa ou assunto..." autocomplete="off" />
|
||||||
<button type="button" class="btn-outline" id="ncf-gestao-search-clear-btn" hidden>Limpar</button>
|
<button type="button" class="btn-outline" id="ncf-gestao-search-clear-btn" hidden>Limpar</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ncf-resumo-wrap">
|
||||||
|
<button type="button" class="btn-outline ncf-resumo-btn" id="ncf-resumo-btn" aria-expanded="false">
|
||||||
|
Resumo <span class="ncf-count" id="ncf-resumo-count"></span>
|
||||||
|
</button>
|
||||||
|
<div class="ncf-resumo-popover" id="ncf-resumo-popover" hidden>
|
||||||
|
<div class="ncf-resumo-popover__preview" id="ncf-resumo-preview"></div>
|
||||||
|
<div class="ncf-resumo-popover__actions">
|
||||||
|
<button type="button" class="btn-solid" id="ncf-resumo-copiar-btn">Copiar texto</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="ncf-section" id="ncf-section-acoes">
|
||||||
|
<div class="ncf-section__header">
|
||||||
|
<h3>Ações</h3>
|
||||||
|
<span class="ncf-count" id="ncf-acoes-count"></span>
|
||||||
|
</div>
|
||||||
|
<div class="ncf-filtro-grupo">
|
||||||
|
<span class="ncf-filtro-grupo__label">Prazo:</span>
|
||||||
|
<div class="ncf-chip-row" id="ncf-filtro-prazo">
|
||||||
|
<button type="button" class="ncf-chip is-active" data-ncf-prazo="vencida">Vencidas</button>
|
||||||
|
<button type="button" class="ncf-chip is-active" data-ncf-prazo="vence_7_dias">Vence em 7 dias</button>
|
||||||
|
<button type="button" class="ncf-chip is-active" data-ncf-prazo="vence_30_dias">Vence em 30 dias</button>
|
||||||
|
<button type="button" class="ncf-chip is-active" data-ncf-prazo="no_prazo">No prazo</button>
|
||||||
|
<button type="button" class="ncf-chip is-active" data-ncf-prazo="sem_vencimento">Sem vencimento</button>
|
||||||
|
<button type="button" class="ncf-chip" data-ncf-prazo="finalizada">Finalizadas</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ncf-filtro-grupo">
|
||||||
|
<span class="ncf-filtro-grupo__label">Tratativa:</span>
|
||||||
|
<div class="ncf-chip-row" id="ncf-filtro-tratativa">
|
||||||
|
<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">
|
||||||
|
<table class="pa-table ncf-table-sortable" id="ncf-table-acoes">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th data-sort="ocorrencia_codigo">Ocorrência/Ação <span class="ncf-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="ocorrencia_area">Área <span class="ncf-sort-icon">↕</span></th>
|
||||||
|
<th data-sort="tipo_acao">Tipo <span class="ncf-sort-icon">↕</span></th>
|
||||||
|
<th class="ncf-th-filterable" data-sort="ocorrencia_tipo_ocorrencia">
|
||||||
|
<span class="ncf-th-sort-label">Tipo de Ocorrência <span class="ncf-sort-icon">↕</span></span>
|
||||||
|
<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">
|
||||||
|
<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="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>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="ncf-acoes-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p class="ncf-empty" id="ncf-acoes-empty" hidden>Nenhuma ação encontrada para este filtro.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="ncf-section" id="ncf-section-sem-analise">
|
<section class="ncf-section" id="ncf-section-sem-analise">
|
||||||
<div class="ncf-section__header">
|
<div class="ncf-section__header">
|
||||||
@ -470,87 +562,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="ncf-empty" id="ncf-analise-sem-acao-empty" hidden>Nenhuma análise sem ação no momento.</p>
|
<p class="ncf-empty" id="ncf-analise-sem-acao-empty" hidden>Nenhuma análise sem ação no momento.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="ncf-section" id="ncf-section-acoes">
|
|
||||||
<div class="ncf-section__header">
|
|
||||||
<h3>Ações</h3>
|
|
||||||
<span class="ncf-count" id="ncf-acoes-count"></span>
|
|
||||||
</div>
|
|
||||||
<div class="ncf-filtro-grupo">
|
|
||||||
<span class="ncf-filtro-grupo__label">Prazo:</span>
|
|
||||||
<div class="ncf-chip-row" id="ncf-filtro-prazo">
|
|
||||||
<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="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="no_prazo">No prazo</button>
|
|
||||||
<button type="button" class="ncf-chip" data-ncf-prazo="sem_vencimento">Sem vencimento</button>
|
|
||||||
<button type="button" class="ncf-chip" data-ncf-prazo="finalizada">Finalizadas</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ncf-filtro-grupo">
|
|
||||||
<span class="ncf-filtro-grupo__label">Tratativa:</span>
|
|
||||||
<div class="ncf-chip-row" id="ncf-filtro-tratativa">
|
|
||||||
<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">
|
|
||||||
<table class="pa-table ncf-table-sortable" id="ncf-table-acoes">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th data-sort="ocorrencia_codigo">Ocorrência/Ação <span class="ncf-sort-icon">↕</span></th>
|
|
||||||
<th data-sort="ocorrencia_area">Área <span class="ncf-sort-icon">↕</span></th>
|
|
||||||
<th data-sort="tipo_acao">Tipo <span class="ncf-sort-icon">↕</span></th>
|
|
||||||
<th class="ncf-th-filterable" data-sort="ocorrencia_tipo_ocorrencia">
|
|
||||||
<span class="ncf-th-sort-label">Tipo de Ocorrência <span class="ncf-sort-icon">↕</span></span>
|
|
||||||
<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">
|
|
||||||
<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="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>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="ncf-acoes-body"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<p class="ncf-empty" id="ncf-acoes-empty" hidden>Nenhuma ação encontrada para este filtro.</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pa-tab-panel" data-ncf-panel="importar" hidden>
|
<div class="pa-tab-panel" data-ncf-panel="importar" hidden>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user