aplicado Filtro

This commit is contained in:
Luiz Silva 2026-01-29 15:33:42 -03:00
parent e7357e064a
commit 1e3c4026e8
12 changed files with 1507 additions and 12626 deletions

View file

@ -115,8 +115,7 @@ import type { EliColuna } from "./types-eli-tabela";
/** Tipos da configuração/contrato da tabela */
import type { EliTabelaConsulta } from "./types-eli-tabela";
import type { tipoFiltro } from "./types-eli-tabela";
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas";
import { operadores as Operadores } from "p-comuns";
// operadores usados no tipo de configuração; o operador aplicado vem travado no filtroAvancado.
import {
carregarConfigColunas,
salvarConfigColunas,
@ -177,14 +176,10 @@ export default defineComponent({
const modalFiltroAberto = ref(false);
type LinhaFiltroUI<T> = {
coluna: keyof T;
operador: keyof typeof Operadores;
entrada: ComponenteEntrada;
valor: any;
};
const filtrosUi = ref<Array<LinhaFiltroUI<any>>>(
carregarFiltroAvancado<any>(props.tabela.nome) as any
);
const filtrosUi = ref<Array<LinhaFiltroUI<any>>>(carregarFiltroAvancado<any>(props.tabela.nome) as any);
function abrirModalFiltro() {
modalFiltroAberto.value = true;
@ -209,15 +204,22 @@ export default defineComponent({
}
const filtrosAvancadosAtivos = computed<tipoFiltro[]>(() => {
// converte UI -> tipoFiltro (p-comuns)
// Operador vem travado na definição (`tabela.filtroAvancado`).
const base = (props.tabela.filtroAvancado ?? []) as Array<{ coluna: string; operador: any }>;
return (filtrosUi.value ?? [])
.filter((f) => f && f.coluna && f.operador)
.map((f) => ({
coluna: String(f.coluna),
operador: f.operador as any,
valor: f.valor,
// sem OR no primeiro momento
})) as tipoFiltro[];
.filter((f) => f && f.coluna !== undefined)
.map((f) => {
const b = base.find((x) => String(x.coluna) === String(f.coluna));
if (!b) return null;
return {
coluna: String(b.coluna),
operador: b.operador as any,
valor: (f as any).valor,
} as tipoFiltro;
})
.filter(Boolean) as tipoFiltro[];
});
/** Alias reativo da prop tabela */

View file

@ -14,19 +14,7 @@
</div>
<div v-else class="eli-tabela-modal-filtro__lista">
<div v-for="(linha, idx) in linhas" :key="idx" class="eli-tabela-modal-filtro__linha">
<select v-model="linha.coluna" class="eli-tabela-modal-filtro__select">
<option v-for="opt in colunasDisponiveis" :key="String(opt)" :value="opt">
{{ String(opt) }}
</option>
</select>
<select v-model="linha.operador" class="eli-tabela-modal-filtro__select">
<option v-for="op in operadoresDisponiveis" :key="op" :value="op">
{{ op }}
</option>
</select>
<div v-for="(linha, idx) in linhas" :key="String(linha.coluna)" class="eli-tabela-modal-filtro__linha">
<div class="eli-tabela-modal-filtro__entrada">
<component
:is="componenteEntrada(linha.entrada)"
@ -49,8 +37,19 @@
</div>
<div class="eli-tabela-modal-filtro__acoes">
<button type="button" class="eli-tabela-modal-filtro__botao" @click="adicionar" :disabled="!filtrosBase.length">
Adicionar filtro
<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) }}
</option>
</select>
<button
type="button"
class="eli-tabela-modal-filtro__botao"
@click="adicionar"
:disabled="!colunaParaAdicionar"
>
Adicionar
</button>
</div>
</div>
@ -71,20 +70,17 @@
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watch } from "vue";
import { operadores as Operadores } from "p-comuns";
import { computed, defineComponent, PropType, ref, watch } from "vue";
import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora } from "../EliEntrada";
import type { ComponenteEntrada, TipoEntrada } from "../EliEntrada/tiposEntradas";
import type { EliTabelaConsulta } from "./types-eli-tabela";
type Operador = keyof typeof Operadores;
type FiltroBase<T> = NonNullable<EliTabelaConsulta<T>["filtroAvancado"]>[number];
type LinhaFiltro<T> = {
coluna: keyof T;
operador: Operador;
entrada: ComponenteEntrada;
operador: string;
valor: any;
};
@ -92,6 +88,11 @@ function isTipoEntrada(v: unknown): v is TipoEntrada {
return v === "texto" || v === "numero" || v === "dataHora";
}
function rotuloDoFiltro(f: FiltroBase<any>) {
const rotulo = (f?.entrada?.[1] as any)?.rotulo;
return rotulo ? String(rotulo) : String(f?.coluna ?? "Filtro");
}
export default defineComponent({
name: "EliTabelaModalFiltroAvancado",
props: {
@ -113,9 +114,14 @@ export default defineComponent({
setup(props, { emit }) {
const linhas = ref<Array<LinhaFiltro<any>>>([]);
const operadoresDisponiveis = Object.keys(Operadores) as Operador[];
const colunaParaAdicionar = ref<string>("");
const colunasDisponiveis = ref<string[]>([]);
const colunasDisponiveis = computed(() => (props.filtrosBase ?? []).map((b) => String(b.coluna)));
const opcoesParaAdicionar = computed(() => {
const usadas = new Set(linhas.value.map((l) => String(l.coluna)));
return (props.filtrosBase ?? []).filter((b) => !usadas.has(String(b.coluna)));
});
function componenteEntrada(entrada: ComponenteEntrada) {
const tipo = entrada?.[0];
@ -125,12 +131,8 @@ export default defineComponent({
}
function opcoesEntrada(entrada: ComponenteEntrada) {
const opcoes = entrada?.[1] as any;
// garante rotulo para não ficar vazio visualmente dentro do modal
if (opcoes && typeof opcoes === "object" && !opcoes.rotulo) {
return { ...opcoes, rotulo: "Valor" };
}
return opcoes ?? { rotulo: "Valor" };
// o rótulo vem do próprio componente (entrada[1].rotulo)
return (entrada?.[1] as any) ?? { rotulo: "" };
}
function valorInicialPorEntrada(entrada: ComponenteEntrada) {
@ -141,13 +143,13 @@ export default defineComponent({
function normalizarModelo() {
const base = props.filtrosBase ?? [];
colunasDisponiveis.value = base.map((b) => String(b.coluna));
const modelo = Array.isArray(props.modelo) ? props.modelo : [];
linhas.value = modelo.map((m: any) => {
const entrada = m.entrada as ComponenteEntrada;
const col = m.coluna as any;
const op = (m.operador ?? "=") as Operador;
// operador vem travado no base
const baseItem = base.find((b) => String(b.coluna) === String(m.coluna)) ?? base[0];
const entrada = (baseItem?.entrada ?? m.entrada) as ComponenteEntrada;
const col = (baseItem?.coluna ?? m.coluna) as any;
const op = String(baseItem?.operador ?? "=");
const val = m.valor ?? valorInicialPorEntrada(entrada);
return {
@ -159,26 +161,12 @@ export default defineComponent({
});
// se vazio e existe base, adiciona 1 linha default
if (!linhas.value.length && base.length) {
const b0 = base[0];
linhas.value = [
{
coluna: b0.coluna as any,
operador: (b0.operador as any) ?? "=",
entrada: b0.entrada,
valor: valorInicialPorEntrada(b0.entrada),
},
];
}
// não auto-adiciona; usuário escolhe quais filtros quer usar
// se algum filtro mudou a coluna para valor inválido, ajusta
for (const l of linhas.value) {
if (!colunasDisponiveis.value.includes(String(l.coluna))) {
l.coluna = (base[0]?.coluna as any) ?? l.coluna;
}
if (!operadoresDisponiveis.includes(l.operador)) {
l.operador = "=";
}
if (!colunasDisponiveis.value.includes(String(l.coluna))) continue;
l.operador = String((base.find((b) => String(b.coluna) === String(l.coluna))?.operador ?? "="));
// sanity
if (l.entrada && !isTipoEntrada(l.entrada[0])) {
l.entrada = ["texto", { rotulo: "Valor" }] as any;
@ -195,14 +183,20 @@ export default defineComponent({
);
function adicionar() {
if (!props.filtrosBase.length) return;
const b0 = props.filtrosBase[0];
if (!colunaParaAdicionar.value) return;
const b0 = (props.filtrosBase ?? []).find((b) => String(b.coluna) === String(colunaParaAdicionar.value));
if (!b0) return;
// evita repetição
if (linhas.value.some((l) => String(l.coluna) === String(b0.coluna))) return;
linhas.value.push({
coluna: b0.coluna as any,
operador: (b0.operador as any) ?? "=",
entrada: b0.entrada,
operador: String(b0.operador ?? "="),
valor: valorInicialPorEntrada(b0.entrada),
});
colunaParaAdicionar.value = "";
}
function remover(idx: number) {
@ -222,8 +216,6 @@ export default defineComponent({
"salvar",
linhas.value.map((l) => ({
coluna: l.coluna,
operador: l.operador,
entrada: l.entrada,
valor: l.valor,
}))
);
@ -231,15 +223,17 @@ export default defineComponent({
return {
linhas,
operadoresDisponiveis,
colunasDisponiveis,
opcoesParaAdicionar,
colunaParaAdicionar,
componenteEntrada,
opcoesEntrada,
adicionar,
remover,
// exibimos operador fixo só como texto
emitFechar,
emitSalvar,
emitLimpar,
rotuloDoFiltro,
};
},
});
@ -312,7 +306,7 @@ export default defineComponent({
.eli-tabela-modal-filtro__linha {
display: grid;
grid-template-columns: 220px 120px 1fr 34px;
grid-template-columns: 1fr 34px;
gap: 10px;
align-items: center;
}

View file

@ -1,6 +1,7 @@
import type { EliTabelaConsulta } from "./types-eli-tabela";
export type EliTabelaFiltroAvancadoSalvo<T> = NonNullable<EliTabelaConsulta<T>["filtroAvancado"]>;
export type EliTabelaFiltroAvancadoSalvo<T> = Array<{
coluna: keyof T
valor: any
}>;
function key(nomeTabela: string) {
return `eli_tabela:${nomeTabela}:filtro_avancado`;

View file

@ -328,16 +328,19 @@ export default defineComponent({
coluna: "empreendedor",
operador: "like",
entrada: ["texto", { rotulo: "Empreendedor" }] as ComponenteEntrada,
rotulo: 'Empreendedor'
},
{
coluna: "documento",
operador: "like",
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as ComponenteEntrada,
'rotulo': 'Documento'
},
{
coluna: "email",
operador: "like",
entrada: ["texto", { rotulo: "E-mail", formato: "email" }] as ComponenteEntrada,
rotulo: 'E-mail'
},
],
consulta: async (parametrosConsulta) => {