build
This commit is contained in:
parent
63d943d0df
commit
f396203085
22 changed files with 1476 additions and 1357 deletions
|
|
@ -1,6 +1,12 @@
|
|||
<template>
|
||||
<div class="eli-tabela">
|
||||
<EliTabelaDebug :isDev="isDev" :menuAberto="menuAberto" :menuPopupPos="menuPopupPos" />
|
||||
<EliTabelaDebug :isDev="isDev" :menuAberto="menuAberto" :menuPopupPos="menuPopupPos">
|
||||
<div>paginaAtual: {{ paginaAtual }}</div>
|
||||
<div>limit: {{ registrosPorConsulta }}</div>
|
||||
<div>texto_busca: {{ (valorBusca || '').trim() }}</div>
|
||||
<div>filtrosAvancadosAtivos: {{ JSON.stringify(filtrosAvancadosAtivos) }}</div>
|
||||
<div>quantidadeTotal: {{ quantidade }}</div>
|
||||
</EliTabelaDebug>
|
||||
|
||||
<EliTabelaEstados
|
||||
v-if="carregando || Boolean(erro) || !linhas.length"
|
||||
|
|
@ -193,14 +199,22 @@ export default defineComponent({
|
|||
filtrosUi.value = [];
|
||||
limparFiltroAvancado(props.tabela.nome);
|
||||
modalFiltroAberto.value = false;
|
||||
// Se o usuário estiver usando filtro avançado, a busca deixa de ter efeito.
|
||||
// Mantemos a regra combinatória (busca tem prioridade), então limpamos a busca.
|
||||
valorBusca.value = "";
|
||||
if (paginaAtual.value !== 1) paginaAtual.value = 1;
|
||||
else void carregar();
|
||||
}
|
||||
|
||||
function salvarFiltrosAvancados(novo: any[]) {
|
||||
filtrosUi.value = (novo ?? []) as any;
|
||||
salvarFiltroAvancado(props.tabela.nome, (novo ?? []) as any);
|
||||
modalFiltroAberto.value = false;
|
||||
// Ao aplicar filtros, limpamos a busca para garantir que os filtros sejam efetivos.
|
||||
// (busca tem prioridade sobre filtros)
|
||||
valorBusca.value = "";
|
||||
if (paginaAtual.value !== 1) paginaAtual.value = 1;
|
||||
else void carregar();
|
||||
}
|
||||
|
||||
const filtrosAvancadosAtivos = computed<tipoFiltro[]>(() => {
|
||||
|
|
@ -351,85 +365,20 @@ export default defineComponent({
|
|||
return 10;
|
||||
});
|
||||
|
||||
function aplicarFiltroTexto(linhasIn: unknown[]) {
|
||||
const q = (valorBusca.value ?? "").trim().toLowerCase();
|
||||
if (!q) return linhasIn;
|
||||
// filtro simples: stringifica o objeto
|
||||
return linhasIn.filter((l) => JSON.stringify(l).toLowerCase().includes(q));
|
||||
}
|
||||
|
||||
function compararOperador(operador: string, valorLinha: any, valorFiltro: any): boolean {
|
||||
switch (operador) {
|
||||
case "=":
|
||||
return valorLinha == valorFiltro;
|
||||
case "!=":
|
||||
return valorLinha != valorFiltro;
|
||||
case ">":
|
||||
return Number(valorLinha) > Number(valorFiltro);
|
||||
case ">=":
|
||||
return Number(valorLinha) >= Number(valorFiltro);
|
||||
case "<":
|
||||
return Number(valorLinha) < Number(valorFiltro);
|
||||
case "<=":
|
||||
return Number(valorLinha) <= Number(valorFiltro);
|
||||
case "like": {
|
||||
const a = String(valorLinha ?? "").toLowerCase();
|
||||
const b = String(valorFiltro ?? "").toLowerCase();
|
||||
return a.includes(b);
|
||||
}
|
||||
case "in": {
|
||||
// aceita "a,b,c" ou array
|
||||
const arr = Array.isArray(valorFiltro)
|
||||
? valorFiltro
|
||||
: String(valorFiltro ?? "")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
return arr.includes(String(valorLinha));
|
||||
}
|
||||
case "isNull":
|
||||
return valorLinha === null || valorLinha === undefined || valorLinha === "";
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function aplicarFiltroAvancado(linhasIn: unknown[]) {
|
||||
const filtros = filtrosAvancadosAtivos.value;
|
||||
if (!filtros.length) return linhasIn;
|
||||
|
||||
return linhasIn.filter((l: any) => {
|
||||
return filtros.every((f) => {
|
||||
const vLinha = l?.[f.coluna as any];
|
||||
return compararOperador(String(f.operador), vLinha, (f as any).valor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const linhasFiltradas = computed(() => {
|
||||
const base = linhas.value ?? [];
|
||||
return aplicarFiltroAvancado(aplicarFiltroTexto(base));
|
||||
});
|
||||
|
||||
/** Quantidade agora segue a filtragem local */
|
||||
const quantidadeFiltrada = computed(() => linhasFiltradas.value.length);
|
||||
|
||||
/** Total de páginas calculado com base no filtrado */
|
||||
/** Total de páginas calculado com base no total retornado pela API */
|
||||
const totalPaginas = computed(() => {
|
||||
const limite = registrosPorConsulta.value;
|
||||
if (!limite || limite <= 0) return 1;
|
||||
|
||||
const total = quantidadeFiltrada.value;
|
||||
const total = quantidade.value ?? 0;
|
||||
if (!total) return 1;
|
||||
|
||||
return Math.max(1, Math.ceil(total / limite));
|
||||
});
|
||||
|
||||
const linhasPaginadas = computed(() => {
|
||||
const limite = Math.max(1, registrosPorConsulta.value);
|
||||
const offset = (paginaAtual.value - 1) * limite;
|
||||
return linhasFiltradas.value.slice(offset, offset + limite);
|
||||
});
|
||||
/** As linhas já vêm paginadas do backend */
|
||||
const linhasPaginadas = computed(() => linhas.value ?? []);
|
||||
|
||||
/** Quantidade exibida é a quantidade total retornada pela consulta */
|
||||
const quantidadeFiltrada = computed(() => quantidade.value ?? 0);
|
||||
|
||||
/** Indica se existem ações por linha */
|
||||
const temAcoes = computed(() => (props.tabela.acoesLinha ?? []).length > 0);
|
||||
|
|
@ -594,12 +543,11 @@ export default defineComponent({
|
|||
menuAberto.value = null;
|
||||
linhasExpandidas.value = {};
|
||||
|
||||
// Em modo simulação (filtro local), sempre buscamos a lista completa.
|
||||
// A paginação é aplicada APÓS a filtragem.
|
||||
const limite = Math.max(1, registrosPorConsulta.value);
|
||||
const offset = 0;
|
||||
const offset = (paginaAtual.value - 1) * limite;
|
||||
|
||||
const parametrosConsulta: {
|
||||
filtros?: tipoFiltro[];
|
||||
coluna_ordem?: never;
|
||||
direcao_ordem?: "asc" | "desc";
|
||||
offSet: number;
|
||||
|
|
@ -607,16 +555,28 @@ export default defineComponent({
|
|||
texto_busca?: string;
|
||||
} = {
|
||||
offSet: offset,
|
||||
limit: 999999,
|
||||
limit: limite,
|
||||
};
|
||||
|
||||
// texto_busca ficará somente para filtragem local.
|
||||
// Regra combinatória definida: busca tem prioridade.
|
||||
const busca = (valorBusca.value ?? "").trim();
|
||||
if (busca) {
|
||||
parametrosConsulta.texto_busca = busca;
|
||||
} else {
|
||||
const filtros = filtrosAvancadosAtivos.value;
|
||||
if (filtros.length) parametrosConsulta.filtros = filtros;
|
||||
}
|
||||
|
||||
if (colunaOrdenacao.value) {
|
||||
parametrosConsulta.coluna_ordem = colunaOrdenacao.value as never;
|
||||
parametrosConsulta.direcao_ordem = direcaoOrdenacao.value;
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[EliTabela] consulta(parametros)", parametrosConsulta);
|
||||
}
|
||||
|
||||
try {
|
||||
const tabelaConfig = props.tabela;
|
||||
const res = await tabelaConfig.consulta(parametrosConsulta);
|
||||
|
|
@ -631,16 +591,13 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const valores = res.valor?.valores ?? [];
|
||||
const total = valores.length;
|
||||
const total = (res.valor as any)?.quantidade ?? valores.length;
|
||||
|
||||
linhas.value = valores;
|
||||
quantidade.value = total;
|
||||
quantidade.value = Number(total) || 0;
|
||||
|
||||
const totalPaginasRecalculado = Math.max(1, Math.ceil((quantidadeFiltrada.value || 0) / limite));
|
||||
if (paginaAtual.value > totalPaginasRecalculado) {
|
||||
paginaAtual.value = totalPaginasRecalculado;
|
||||
return;
|
||||
}
|
||||
const totalPaginasRecalculado = Math.max(1, Math.ceil((quantidade.value || 0) / limite));
|
||||
if (paginaAtual.value > totalPaginasRecalculado) paginaAtual.value = totalPaginasRecalculado;
|
||||
|
||||
const acoesLinhaConfiguradas = tabelaConfig.acoesLinha ?? [];
|
||||
if (!acoesLinhaConfiguradas.length) {
|
||||
|
|
@ -720,10 +677,7 @@ export default defineComponent({
|
|||
|
||||
/** Watch: mudança de página dispara nova consulta */
|
||||
watch(paginaAtual, (nova, antiga) => {
|
||||
// paginação local não precisa recarregar
|
||||
if (nova !== antiga) {
|
||||
// noop
|
||||
}
|
||||
if (nova !== antiga) void carregar();
|
||||
});
|
||||
|
||||
/** Watch: troca de configuração reseta estados e recarrega */
|
||||
|
|
@ -774,6 +728,7 @@ export default defineComponent({
|
|||
erro,
|
||||
linhas,
|
||||
linhasPaginadas,
|
||||
filtrosAvancadosAtivos,
|
||||
quantidadeFiltrada,
|
||||
quantidade,
|
||||
menuAberto,
|
||||
|
|
@ -782,6 +737,7 @@ export default defineComponent({
|
|||
colunaOrdenacao,
|
||||
direcaoOrdenacao,
|
||||
totalPaginas,
|
||||
registrosPorConsulta,
|
||||
|
||||
// computed
|
||||
exibirBusca,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<div><b>EliTabela debug</b></div>
|
||||
<div>menuAberto: {{ menuAberto }}</div>
|
||||
<div>menuPos: top={{ menuPopupPos.top }}, left={{ menuPopupPos.left }}</div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,11 @@
|
|||
</div>
|
||||
|
||||
<div class="eli-tabela-modal-filtro__acoes">
|
||||
<select v-model="colunaParaAdicionar" class="eli-tabela-modal-filtro__select" :disabled="!opcoesParaAdicionar.length">
|
||||
<select
|
||||
v-model="colunaParaAdicionar"
|
||||
class="eli-tabela-modal-filtro__select"
|
||||
:disabled="!opcoesParaAdicionar.length"
|
||||
>
|
||||
<option disabled value="">Selecione um filtro…</option>
|
||||
<option v-for="o in opcoesParaAdicionar" :key="String(o.coluna)" :value="String(o.coluna)">
|
||||
{{ rotuloDoFiltro(o) }}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
<template>
|
||||
|
||||
<!-- TODO: Validar de ação está cehgando aqui-->
|
||||
<button
|
||||
v-if="dados?.acao"
|
||||
type="button"
|
||||
class="eli-tabela__texto-truncado eli-tabela__celula-link"
|
||||
:title="dados?.texto"
|
||||
@click.stop.prevent="dados.acao()"
|
||||
@click.stop.prevent="dados?.acao?.()"
|
||||
>
|
||||
{{ dados?.texto }}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ export type EliTabelaConsulta<T> = {
|
|||
*/
|
||||
consulta: (parametrosConsulta?: {
|
||||
|
||||
//Todo: Esse filtros são recebido do processamento de filtro avandado
|
||||
|
||||
filtros?: tipoFiltro[]
|
||||
coluna_ordem?: keyof T;
|
||||
|
|
@ -125,12 +124,19 @@ export type EliTabelaConsulta<T> = {
|
|||
rotulo: string;
|
||||
/** Função executada ao clicar no botão. */
|
||||
acao: () => void;
|
||||
|
||||
/**
|
||||
* Callback opcional para forçar atualização da consulta.
|
||||
* Observação: o componente `EliTabela` pode ignorar isso dependendo do modo de uso.
|
||||
*/
|
||||
atualizarConsulta?: () => Promise<void>
|
||||
/**
|
||||
* Callback opcional para permitir editar a lista localmente (sem refazer consulta).
|
||||
* Observação: o componente `EliTabela` pode ignorar isso dependendo do modo de uso.
|
||||
*/
|
||||
editarLista?: (lista: T[]) => Promise<T[]>
|
||||
}[];
|
||||
|
||||
/** configuração para aplicação dos filtros padrões */
|
||||
|
||||
// Todo: quando exite aparace ap lado do obtão coluna o potão filtro avançado, onde abre um modal com dua colunas de compoentes que são contruidas conforme esse padrão
|
||||
// todo: Os filtros criados deverão ser salvo em local storagem como um objeto tipofiltro[]
|
||||
filtroAvancado?: {
|
||||
rotulo: string,
|
||||
coluna: keyof T,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue