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,
|
||||
|
|
|
|||
|
|
@ -17,8 +17,13 @@ export { EliOlaMundo };
|
|||
export { EliBotao };
|
||||
export { EliBadge };
|
||||
export { EliCartao };
|
||||
export { EliTabela };
|
||||
export { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora, EliEntradaParagrafo, EliEntradaSelecao };
|
||||
|
||||
// Exportar tudo (componentes + types + helpers) de Tabela e Entrada
|
||||
export * from "./componentes/EliTabela";
|
||||
export * from "./componentes/EliEntrada";
|
||||
|
||||
// Exportar tipos compartilhados (ex: CartaoStatus)
|
||||
export * from "./tipos";
|
||||
|
||||
const EliVue: Plugin = {
|
||||
install(app: App) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { BadgeCheck, Eye, Pencil, Plus, Trash2 } from "lucide-vue-next";
|
|||
import { celulaTabela, EliTabela } from "@/componentes/EliTabela";
|
||||
import type { ComponenteEntrada } from "@/componentes/EliEntrada/tiposEntradas";
|
||||
import type { EliTabelaConsulta } from "@/componentes/EliTabela";
|
||||
import type { tipoFiltro } from "@/componentes/EliTabela/types-eli-tabela";
|
||||
|
||||
type Linha = {
|
||||
empreendedor: string;
|
||||
|
|
@ -324,21 +325,61 @@ export default defineComponent({
|
|||
});
|
||||
};
|
||||
|
||||
const ordenarLinhas = (
|
||||
linhas: Linha[],
|
||||
parametros?: { coluna_ordem?: keyof Linha; direcao_ordem?: "asc" | "desc"; texto_busca?: string }
|
||||
) => {
|
||||
const listaFiltrada = filtrarPorBusca(linhas, parametros?.texto_busca);
|
||||
|
||||
if (!parametros?.coluna_ordem) {
|
||||
return listaFiltrada;
|
||||
const 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": {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const filtrarPorFiltrosAvancados = (linhas: Linha[], filtros?: tipoFiltro[]) => {
|
||||
const lista = [...linhas];
|
||||
if (!filtros?.length) return lista;
|
||||
|
||||
return lista.filter((linha: any) => {
|
||||
return filtros.every((f) => {
|
||||
const vLinha = linha?.[String((f as any).coluna)];
|
||||
return compararOperador(String((f as any).operador), vLinha, (f as any).valor);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const ordenarLinhas = (linhas: Linha[], parametros?: { coluna_ordem?: keyof Linha; direcao_ordem?: "asc" | "desc" }) => {
|
||||
if (!parametros?.coluna_ordem) return [...linhas];
|
||||
|
||||
const direcao = parametros.direcao_ordem ?? "asc";
|
||||
const chave = parametros.coluna_ordem;
|
||||
const multiplicador = direcao === "asc" ? 1 : -1;
|
||||
|
||||
return [...listaFiltrada].sort((a, b) => {
|
||||
return [...linhas].sort((a, b) => {
|
||||
const valorA = a[chave];
|
||||
const valorB = b[chave];
|
||||
|
||||
|
|
@ -483,10 +524,23 @@ export default defineComponent({
|
|||
},
|
||||
],
|
||||
consulta: async (parametrosConsulta) => {
|
||||
// No filtro avançado (modo simulação), a EliTabela busca a lista completa
|
||||
// e pagina/filtra localmente.
|
||||
const ordenadas = ordenarLinhas(linhasPadrao.value, parametrosConsulta);
|
||||
const valores = ordenadas;
|
||||
// Agora a EliTabela envia paginação/ordenação/busca OU filtros avançados para a consulta.
|
||||
// (busca tem prioridade; quando existe texto_busca, filtros não vêm no payload)
|
||||
|
||||
const limite = Math.max(1, Number(parametrosConsulta?.limit ?? 10));
|
||||
const offset = Math.max(0, Number(parametrosConsulta?.offSet ?? 0));
|
||||
|
||||
// 1) filtra (busca OU filtro avançado)
|
||||
const base = [...linhasPadrao.value];
|
||||
const filtradas = parametrosConsulta?.texto_busca
|
||||
? filtrarPorBusca(base, parametrosConsulta.texto_busca)
|
||||
: filtrarPorFiltrosAvancados(base, (parametrosConsulta as any)?.filtros);
|
||||
|
||||
// 2) ordena
|
||||
const ordenadas = ordenarLinhas(filtradas, parametrosConsulta);
|
||||
|
||||
// 3) pagina
|
||||
const valores = ordenadas.slice(offset, offset + limite);
|
||||
|
||||
return {
|
||||
cod: codigosResposta.sucesso,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue