implementado filtro26

This commit is contained in:
Luiz Silva 2026-02-17 11:33:44 -03:00
parent ba0e116a92
commit 5124844773
18 changed files with 3247 additions and 3257 deletions

View file

@ -97,10 +97,12 @@ export type tipoEliTabelaConsulta<T> = {
// Definição de Filtro Avançado // Definição de Filtro Avançado
filtroAvancado?: Array<{ filtroAvancado?: Array<{
chave: string;
rotulo: string; rotulo: string;
coluna: keyof T; /** Função que gera o filtro com base no valor recebido do input */
operador: string; // ex: "=", "like", ">=" filtro: (valor: unknown) => tipoFiltro<T>;
entrada: any; // Definição do componente, ex: ["texto", { ... }] /** Definição do componente de entrada, ex: ["texto", { ... }] */
entrada: any;
}>; }>;
}; };
``` ```
@ -129,7 +131,7 @@ Use o helper `celulaTabela` para criar definições de células com segurança d
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
import { EliTabela, celulaTabela } from "eli-vue"; import { EliTabela, celulaTabela, criarFiltro26 } from "eli-vue";
import type { tipoEliTabelaConsulta } from "eli-vue"; import type { tipoEliTabelaConsulta } from "eli-vue";
import { UsuarioService } from "@/services/UsuarioService"; import { UsuarioService } from "@/services/UsuarioService";
import { BadgeCheck, Pencil, Plus } from "lucide-vue-next"; import { BadgeCheck, Pencil, Plus } from "lucide-vue-next";
@ -170,15 +172,16 @@ const tabelaConfig: tipoEliTabelaConsulta<Usuario> = {
filtroAvancado: [ filtroAvancado: [
{ {
rotulo: "Nome", rotulo: "Nome",
coluna: "nome", chave: "nome",
operador: "like", // Função que retorna o objeto de filtro estruturado
// definição do input: ["tipo", opcoes] filtro: (valor: unknown) => criarFiltro26({ nome: { like: valor as string } }),
// Definição do input: ["tipo", opcoes]
entrada: ["texto", { rotulo: "Nome do usuário" }] as any entrada: ["texto", { rotulo: "Nome do usuário" }] as any
}, },
{ {
rotulo: "Ativo?", rotulo: "Ativo?",
coluna: "ativo", chave: "ativo",
operador: "=", filtro: (valor: unknown) => criarFiltro26({ ativo: { "=": valor } }),
entrada: ["selecao", { entrada: ["selecao", {
rotulo: "Status", rotulo: "Status",
itens: () => [ itens: () => [

6068
dist/eli-vue.es.js vendored

File diff suppressed because one or more lines are too long

40
dist/eli-vue.umd.js vendored

File diff suppressed because one or more lines are too long

View file

@ -18,12 +18,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
erro: import("vue").Ref<string | null, string | null>; erro: import("vue").Ref<string | null, string | null>;
linhas: import("vue").Ref<unknown[], unknown[]>; linhas: import("vue").Ref<unknown[], unknown[]>;
linhasPaginadas: import("vue").ComputedRef<unknown[]>; linhasPaginadas: import("vue").ComputedRef<unknown[]>;
filtrosAvancadosAtivos: import("vue").ComputedRef<{ filtrosAvancadosAtivos: import("vue").ComputedRef<tipoFiltro<any>[]>;
coluna: string;
valor: any;
operador: "=" | "!=" | ">" | ">=" | "<" | "<=" | "like" | "in" | "isNull";
ou?: boolean | undefined;
}[]>;
quantidadeFiltrada: import("vue").ComputedRef<number>; quantidadeFiltrada: import("vue").ComputedRef<number>;
quantidade: import("vue").Ref<number, number>; quantidade: import("vue").Ref<number, number>;
menuAberto: import("vue").Ref<number | null, number | null>; menuAberto: import("vue").Ref<number | null, number | null>;
@ -60,7 +55,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
parametrosConsultaAtuais: import("vue").ComputedRef<{ parametrosConsultaAtuais: import("vue").ComputedRef<{
atualizarConsulta: () => Promise<void>; atualizarConsulta: () => Promise<void>;
editarLista: (novaLista: any[]) => Promise<any[]>; editarLista: (novaLista: any[]) => Promise<any[]>;
filtros?: tipoFiltro[]; filtros?: import("p-comuns").tipoFiltro26<any>[] | undefined;
coluna_ordem?: string | number | symbol | undefined; coluna_ordem?: string | number | symbol | undefined;
direcao_ordem?: "asc" | "desc"; direcao_ordem?: "asc" | "desc";
offSet?: number; offSet?: number;
@ -88,13 +83,13 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
salvarModalColunas: (cfg: EliTabelaColunasConfig) => void; salvarModalColunas: (cfg: EliTabelaColunasConfig) => void;
modalFiltroAberto: import("vue").Ref<boolean, boolean>; modalFiltroAberto: import("vue").Ref<boolean, boolean>;
filtrosUi: import("vue").Ref<{ filtrosUi: import("vue").Ref<{
coluna: string | number | symbol; chave: string;
valor: any; valor: any;
}[], { }[], {
coluna: string | number | symbol; chave: string;
valor: any; valor: any;
}[] | { }[] | {
coluna: string | number | symbol; chave: string;
valor: any; valor: any;
}[]>; }[]>;
salvarFiltrosAvancados: (novo: any[]) => void; salvarFiltrosAvancados: (novo: any[]) => void;
@ -861,8 +856,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
}; };
filtrosBase: { filtrosBase: {
type: PropType<Array<{ type: PropType<Array<{
coluna: string | number | symbol; chave: string;
operador: import("p-comuns").operadores | keyof typeof import("p-comuns").operadores; rotulo: string;
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada; entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
}>>; }>>;
required: true; required: true;
@ -873,7 +869,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
}; };
}>, { }>, {
linhas: import("vue").Ref<{ linhas: import("vue").Ref<{
coluna: string | number | symbol; chave: string;
entrada: readonly ["texto", { entrada: readonly ["texto", {
rotulo: string; rotulo: string;
placeholder?: string | undefined; placeholder?: string | undefined;
@ -928,15 +924,13 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
densidade?: import("../..").CampoDensidade | undefined; densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined; variante?: import("../..").CampoVariante | undefined;
}]; }];
operador: string;
valor: any; valor: any;
}[], { }[], {
coluna: string | number | symbol; chave: string;
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada; entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
operador: string;
valor: any; valor: any;
}[] | { }[] | {
coluna: string | number | symbol; chave: string;
entrada: readonly ["texto", { entrada: readonly ["texto", {
rotulo: string; rotulo: string;
placeholder?: string | undefined; placeholder?: string | undefined;
@ -991,15 +985,15 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
densidade?: import("../..").CampoDensidade | undefined; densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined; variante?: import("../..").CampoVariante | undefined;
}]; }];
operador: string;
valor: any; valor: any;
}[]>; }[]>;
opcoesParaAdicionar: import("vue").ComputedRef<{ opcoesParaAdicionar: import("vue").ComputedRef<{
coluna: string | number | symbol; chave: string;
operador: import("p-comuns").operadores | keyof typeof import("p-comuns").operadores; rotulo: string;
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada; entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
}[]>; }[]>;
colunaParaAdicionar: import("vue").Ref<string, string>; chaveParaAdicionar: import("vue").Ref<string, string>;
componenteEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => import("vue").DefineComponent<import("vue").ExtractPropTypes<{ componenteEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => import("vue").DefineComponent<import("vue").ExtractPropTypes<{
value: { value: {
type: PropType<string | null | undefined>; type: PropType<string | null | undefined>;
@ -1352,8 +1346,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
emitSalvar: () => void; emitSalvar: () => void;
emitLimpar: () => void; emitLimpar: () => void;
rotuloDoFiltro: (f: { rotuloDoFiltro: (f: {
coluna: string | number | symbol; chave: string;
operador: import("p-comuns").operadores | keyof typeof import("p-comuns").operadores; rotulo: string;
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada; entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
}) => string; }) => string;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, { }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@ -1367,8 +1362,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
}; };
filtrosBase: { filtrosBase: {
type: PropType<Array<{ type: PropType<Array<{
coluna: string | number | symbol; chave: string;
operador: import("p-comuns").operadores | keyof typeof import("p-comuns").operadores; rotulo: string;
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada; entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
}>>; }>>;
required: true; required: true;

View file

@ -2,10 +2,9 @@ import { type PropType } from "vue";
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"; import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas";
import type { tipoEliTabelaConsulta } from "./types-eli-tabela"; import type { tipoEliTabelaConsulta } from "./types-eli-tabela";
type FiltroBase<T> = NonNullable<tipoEliTabelaConsulta<T>["filtroAvancado"]>[number]; type FiltroBase<T> = NonNullable<tipoEliTabelaConsulta<T>["filtroAvancado"]>[number];
type LinhaFiltro<T> = { type LinhaFiltro = {
coluna: keyof T; chave: string;
entrada: ComponenteEntrada; entrada: ComponenteEntrada;
operador: string;
valor: any; valor: any;
}; };
declare function rotuloDoFiltro(f: FiltroBase<any>): string; declare function rotuloDoFiltro(f: FiltroBase<any>): string;
@ -26,7 +25,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
}; };
}>, { }>, {
linhas: import("vue").Ref<{ linhas: import("vue").Ref<{
coluna: string | number | symbol; chave: string;
entrada: readonly ["texto", { entrada: readonly ["texto", {
rotulo: string; rotulo: string;
placeholder?: string | undefined; placeholder?: string | undefined;
@ -81,10 +80,9 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
densidade?: import("../..").CampoDensidade | undefined; densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined; variante?: import("../..").CampoVariante | undefined;
}]; }];
operador: string;
valor: any; valor: any;
}[], LinhaFiltro<any>[] | { }[], LinhaFiltro[] | {
coluna: string | number | symbol; chave: string;
entrada: readonly ["texto", { entrada: readonly ["texto", {
rotulo: string; rotulo: string;
placeholder?: string | undefined; placeholder?: string | undefined;
@ -139,15 +137,15 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
densidade?: import("../..").CampoDensidade | undefined; densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined; variante?: import("../..").CampoVariante | undefined;
}]; }];
operador: string;
valor: any; valor: any;
}[]>; }[]>;
opcoesParaAdicionar: import("vue").ComputedRef<{ opcoesParaAdicionar: import("vue").ComputedRef<{
coluna: string | number | symbol; chave: string;
operador: import("p-comuns").operadores | keyof typeof import("p-comuns").operadores; rotulo: string;
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
entrada: ComponenteEntrada; entrada: ComponenteEntrada;
}[]>; }[]>;
colunaParaAdicionar: import("vue").Ref<string, string>; chaveParaAdicionar: import("vue").Ref<string, string>;
componenteEntrada: (entrada: ComponenteEntrada) => import("vue").DefineComponent<import("vue").ExtractPropTypes<{ componenteEntrada: (entrada: ComponenteEntrada) => import("vue").DefineComponent<import("vue").ExtractPropTypes<{
value: { value: {
type: PropType<string | null | undefined>; type: PropType<string | null | undefined>;

View file

@ -1,6 +1,6 @@
export type EliTabelaFiltroAvancadoSalvo<T> = Array<{ export type EliTabelaFiltroAvancadoSalvo<T> = Array<{
coluna: keyof T; coluna: keyof T;
valor: any; valor: T[keyof T];
}>; }>;
export declare function carregarFiltroAvancado<T>(nomeTabela: string): EliTabelaFiltroAvancadoSalvo<T>; export declare function carregarFiltroAvancado<T>(nomeTabela: string): EliTabelaFiltroAvancadoSalvo<T>;
export declare function salvarFiltroAvancado<T>(nomeTabela: string, filtros: EliTabelaFiltroAvancadoSalvo<T>): void; export declare function salvarFiltroAvancado<T>(nomeTabela: string, filtros: EliTabelaFiltroAvancadoSalvo<T>): void;

View file

@ -1,9 +1,9 @@
import type { LucideIcon } from "lucide-vue-next"; import type { LucideIcon } from "lucide-vue-next";
import type { operadores, zFiltro } from "p-comuns"; import type { tipoFiltro26 } from "p-comuns";
export type tipoFiltro<T> = tipoFiltro26<T>;
import type { tipoResposta } from "p-respostas"; import type { tipoResposta } from "p-respostas";
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"; import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas";
import type { tiposTabelaCelulas, tipoTabelaCelula } from "./celulas/tiposTabelaCelulas"; import type { tiposTabelaCelulas, tipoTabelaCelula } from "./celulas/tiposTabelaCelulas";
export type tipoFiltro = ReturnType<(typeof zFiltro)["parse"]>;
export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> = readonly [ export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> = readonly [
T, T,
tiposTabelaCelulas[T] tiposTabelaCelulas[T]
@ -52,7 +52,7 @@ export type tipoEliTabelaAcao<T> = {
exibir?: boolean | ((linha: T) => Promise<boolean> | boolean); exibir?: boolean | ((linha: T) => Promise<boolean> | boolean);
}; };
export type parametrosConsulta<T> = { export type parametrosConsulta<T> = {
filtros?: tipoFiltro[]; filtros?: tipoFiltro26<T>[];
coluna_ordem?: keyof T; coluna_ordem?: keyof T;
direcao_ordem?: "asc" | "desc"; direcao_ordem?: "asc" | "desc";
offSet?: number; offSet?: number;
@ -115,8 +115,13 @@ export type tipoEliTabelaConsulta<T> = {
}) => void; }) => void;
}[]; }[];
filtroAvancado?: { filtroAvancado?: {
coluna: keyof T; /** Identificador único do filtro (usado para persistência) */
operador: operadores | keyof typeof operadores; chave: string;
/** Rótulo exibido no select de adicionar filtro */
rotulo: string;
/** vai gerar filtro e mescla com os filtros já existentes */
filtro: (valor: unknown) => tipoFiltro26<T>;
/** componente de entrada que vai receber o valor para o filtro */
entrada: ComponenteEntrada; entrada: ComponenteEntrada;
}[]; }[];
}; };

View file

@ -8,6 +8,7 @@ export { EliOlaMundo };
export { EliBotao }; export { EliBotao };
export { EliBadge }; export { EliBadge };
export { EliCartao }; export { EliCartao };
export { criarFiltro26 } from "p-comuns";
export * from "./componentes/EliEntrada"; export * from "./componentes/EliEntrada";
export * from "./componentes/EliTabela"; export * from "./componentes/EliTabela";
export * from "./tipos"; export * from "./tipos";

View file

@ -1,6 +1,6 @@
{ {
"name": "eli-vue", "name": "eli-vue",
"version": "0.1.102", "version": "0.1.105",
"private": false, "private": false,
"main": "./dist/eli-vue.umd.js", "main": "./dist/eli-vue.umd.js",
"module": "./dist/eli-vue.es.js", "module": "./dist/eli-vue.es.js",

14
pnpm-lock.yaml generated
View file

@ -19,7 +19,7 @@ importers:
version: 0.563.0(vue@3.5.28(typescript@5.9.3)) version: 0.563.0(vue@3.5.28(typescript@5.9.3))
p-comuns: p-comuns:
specifier: git+https://git2.idz.one/publico/_comuns.git specifier: git+https://git2.idz.one/publico/_comuns.git
version: git+https://git2.idz.one/publico/_comuns.git#303832ba8c4ee87cbdc6ae80ff12a56da6ce37cf(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)(zod@4.3.6) version: git+https://git2.idz.one/publico/_comuns.git#64ddd6ad8af5e09a81d247fb1b23c2b05731d321(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)(zod@4.3.6)
p-respostas: p-respostas:
specifier: git+https://git2.idz.one/publico/_respostas.git specifier: git+https://git2.idz.one/publico/_respostas.git
version: git+https://git2.idz.one/publico/_respostas.git#8c24d790ace7255404745dcbdf12c5396e8b9843(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0) version: git+https://git2.idz.one/publico/_respostas.git#8c24d790ace7255404745dcbdf12c5396e8b9843(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)
@ -659,9 +659,9 @@ packages:
encoding: encoding:
optional: true optional: true
p-comuns@git+https://git2.idz.one/publico/_comuns.git#303832ba8c4ee87cbdc6ae80ff12a56da6ce37cf: p-comuns@git+https://git2.idz.one/publico/_comuns.git#64ddd6ad8af5e09a81d247fb1b23c2b05731d321:
resolution: {commit: 303832ba8c4ee87cbdc6ae80ff12a56da6ce37cf, repo: https://git2.idz.one/publico/_comuns.git, type: git} resolution: {commit: 64ddd6ad8af5e09a81d247fb1b23c2b05731d321, repo: https://git2.idz.one/publico/_comuns.git, type: git}
version: 0.304.0 version: 0.310.0
peerDependencies: peerDependencies:
cross-fetch: 4.1.0 cross-fetch: 4.1.0
dayjs: ^1.11.18 dayjs: ^1.11.18
@ -1273,14 +1273,14 @@ snapshots:
dependencies: dependencies:
whatwg-url: 5.0.0 whatwg-url: 5.0.0
p-comuns@git+https://git2.idz.one/publico/_comuns.git#303832ba8c4ee87cbdc6ae80ff12a56da6ce37cf(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)(zod@4.1.4): p-comuns@git+https://git2.idz.one/publico/_comuns.git#64ddd6ad8af5e09a81d247fb1b23c2b05731d321(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)(zod@4.1.4):
dependencies: dependencies:
cross-fetch: 4.1.0 cross-fetch: 4.1.0
dayjs: 1.11.19 dayjs: 1.11.19
uuid: 13.0.0 uuid: 13.0.0
zod: 4.1.4 zod: 4.1.4
p-comuns@git+https://git2.idz.one/publico/_comuns.git#303832ba8c4ee87cbdc6ae80ff12a56da6ce37cf(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)(zod@4.3.6): p-comuns@git+https://git2.idz.one/publico/_comuns.git#64ddd6ad8af5e09a81d247fb1b23c2b05731d321(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)(zod@4.3.6):
dependencies: dependencies:
cross-fetch: 4.1.0 cross-fetch: 4.1.0
dayjs: 1.11.19 dayjs: 1.11.19
@ -1289,7 +1289,7 @@ snapshots:
p-respostas@git+https://git2.idz.one/publico/_respostas.git#8c24d790ace7255404745dcbdf12c5396e8b9843(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0): p-respostas@git+https://git2.idz.one/publico/_respostas.git#8c24d790ace7255404745dcbdf12c5396e8b9843(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0):
dependencies: dependencies:
p-comuns: git+https://git2.idz.one/publico/_comuns.git#303832ba8c4ee87cbdc6ae80ff12a56da6ce37cf(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)(zod@4.1.4) p-comuns: git+https://git2.idz.one/publico/_comuns.git#64ddd6ad8af5e09a81d247fb1b23c2b05731d321(cross-fetch@4.1.0)(dayjs@1.11.19)(uuid@13.0.0)(zod@4.1.4)
zod: 4.1.4 zod: 4.1.4
transitivePeerDependencies: transitivePeerDependencies:
- cross-fetch - cross-fetch

View file

@ -193,14 +193,12 @@ export default defineComponent({
/** Filtro avançado (config + estado modal) */ /** Filtro avançado (config + estado modal) */
const modalFiltroAberto = ref(false) const modalFiltroAberto = ref(false)
type LinhaFiltroUI<T> = { type LinhaFiltroUI = {
coluna: keyof T chave: string
// biome-ignore lint/suspicious/noExplicitAny: dynamic value // biome-ignore lint/suspicious/noExplicitAny: dynamic value
valor: any valor: any
} }
const filtrosUi = ref<Array<LinhaFiltroUI>>(
// biome-ignore lint/suspicious/noExplicitAny: dynamic ui
const filtrosUi = ref<Array<LinhaFiltroUI<any>>>(
// biome-ignore lint/suspicious/noExplicitAny: dynamic load // biome-ignore lint/suspicious/noExplicitAny: dynamic load
carregarFiltroAvancado<any>(props.tabela.nome) as any, carregarFiltroAvancado<any>(props.tabela.nome) as any,
) )
@ -238,29 +236,23 @@ export default defineComponent({
else void carregar() else void carregar()
} }
const filtrosAvancadosAtivos = computed<tipoFiltro[]>(() => { // biome-ignore lint/suspicious/noExplicitAny: generic
// Operador vem travado na definição (`tabela.filtroAvancado`). const filtrosAvancadosAtivos = computed<tipoFiltro<any>[]>(() => {
const base = (props.tabela.filtroAvancado ?? []) as Array<{ const base = props.tabela.filtroAvancado ?? []
coluna: string
// biome-ignore lint/suspicious/noExplicitAny: dynamic operator
operador: any
}>
return (filtrosUi.value ?? []) return (
.filter((f) => f && f.coluna !== undefined) (filtrosUi.value ?? [])
.filter((f) => f && f.chave !== undefined)
.map((f) => { .map((f) => {
const b = base.find((x) => String(x.coluna) === String(f.coluna)) const b = base.find((x) => String(x.chave) === String(f.chave))
if (!b) return null if (!b) return null
return { // O filtro já vem pronto da função definida na configuração
coluna: String(b.coluna), return b.filtro(f.valor)
// biome-ignore lint/suspicious/noExplicitAny: dynamic operator
operador: b.operador as any,
// biome-ignore lint/suspicious/noExplicitAny: dynamic value
valor: (f as any).valor,
} as tipoFiltro
}) })
.filter(Boolean) as tipoFiltro[] // biome-ignore lint/suspicious/noExplicitAny: generic
.filter(Boolean) as tipoFiltro<any>[]
)
}) })
/** Alias reativo da prop tabela */ /** Alias reativo da prop tabela */
@ -647,7 +639,8 @@ export default defineComponent({
const offset = (paginaAtual.value - 1) * limite const offset = (paginaAtual.value - 1) * limite
const parametrosConsulta: { const parametrosConsulta: {
filtros?: tipoFiltro[] // biome-ignore lint/suspicious/noExplicitAny: generic
filtros?: tipoFiltro<any>[]
coluna_ordem?: never coluna_ordem?: never
direcao_ordem?: "asc" | "desc" direcao_ordem?: "asc" | "desc"
offSet: number offSet: number

View file

@ -14,11 +14,13 @@
</div> </div>
<div v-else class="eli-tabela-modal-filtro__lista"> <div v-else class="eli-tabela-modal-filtro__lista">
<div v-for="(linha, idx) in linhas" :key="String(linha.coluna)" class="eli-tabela-modal-filtro__linha"> <div v-for="(linha, idx) in linhas" :key="String(linha.chave)" class="eli-tabela-modal-filtro__linha">
<div class="eli-tabela-modal-filtro__entrada"> <div class="eli-tabela-modal-filtro__entrada">
<component <component
:is="componenteEntrada(linha.entrada)" :is="componenteEntrada(linha.entrada)"
v-model:value="linha.valor" :value="linha.valor"
@update:value="(v: any) => (linha.valor = v)"
@input="(v: any) => (linha.valor = v)"
:opcoes="opcoesEntrada(linha.entrada)" :opcoes="opcoesEntrada(linha.entrada)"
density="compact" density="compact"
/> />
@ -38,12 +40,12 @@
<div class="eli-tabela-modal-filtro__acoes"> <div class="eli-tabela-modal-filtro__acoes">
<select <select
v-model="colunaParaAdicionar" v-model="chaveParaAdicionar"
class="eli-tabela-modal-filtro__select" class="eli-tabela-modal-filtro__select"
:disabled="!opcoesParaAdicionar.length" :disabled="!opcoesParaAdicionar.length"
> >
<option disabled value="">Selecione um filtro</option> <option disabled value="">Selecione um filtro</option>
<option v-for="o in opcoesParaAdicionar" :key="String(o.coluna)" :value="String(o.coluna)"> <option v-for="o in opcoesParaAdicionar" :key="String(o.chave)" :value="String(o.chave)">
{{ rotuloDoFiltro(o) }} {{ rotuloDoFiltro(o) }}
</option> </option>
</select> </select>
@ -51,7 +53,7 @@
type="button" type="button"
class="eli-tabela-modal-filtro__botao" class="eli-tabela-modal-filtro__botao"
@click="adicionar" @click="adicionar"
:disabled="!colunaParaAdicionar" :disabled="!chaveParaAdicionar"
> >
Adicionar Adicionar
</button> </button>
@ -80,33 +82,23 @@ import {
EliEntradaNumero, EliEntradaNumero,
EliEntradaTexto, EliEntradaTexto,
} from "../EliEntrada" } from "../EliEntrada"
import type { import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
ComponenteEntrada,
TipoEntrada,
} from "../EliEntrada/tiposEntradas"
import type { tipoEliTabelaConsulta } from "./types-eli-tabela" import type { tipoEliTabelaConsulta } from "./types-eli-tabela"
type FiltroBase<T> = NonNullable< type FiltroBase<T> = NonNullable<
tipoEliTabelaConsulta<T>["filtroAvancado"] tipoEliTabelaConsulta<T>["filtroAvancado"]
>[number] >[number]
type LinhaFiltro<T> = { type LinhaFiltro = {
coluna: keyof T chave: string
entrada: ComponenteEntrada entrada: ComponenteEntrada
operador: string
// biome-ignore lint/suspicious/noExplicitAny: dynamic value // biome-ignore lint/suspicious/noExplicitAny: dynamic value
valor: any valor: any
} }
function isTipoEntrada(v: unknown): v is TipoEntrada {
return v === "texto" || v === "numero" || v === "dataHora"
}
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed // biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
function rotuloDoFiltro(f: FiltroBase<any>) { function rotuloDoFiltro(f: FiltroBase<any>) {
// biome-ignore lint/suspicious/noExplicitAny: dynamic access return f.rotulo
const rotulo = (f?.entrada?.[1] as any)?.rotulo
return rotulo ? String(rotulo) : String(f?.coluna ?? "Filtro")
} }
export default defineComponent({ export default defineComponent({
@ -114,7 +106,7 @@ export default defineComponent({
props: { props: {
aberto: { type: Boolean, required: true }, aberto: { type: Boolean, required: true },
filtrosBase: { filtrosBase: {
// biome-ignore lint/suspicious/noExplicitAny: dynamic filter type // biome-ignore lint/suspicious/noExplicitAny: generic component
type: Array as PropType<Array<FiltroBase<any>>>, type: Array as PropType<Array<FiltroBase<any>>>,
required: true, required: true,
}, },
@ -131,19 +123,18 @@ export default defineComponent({
salvar: (_linhas: any[]) => true, salvar: (_linhas: any[]) => true,
}, },
setup(props, { emit }) { setup(props, { emit }) {
// biome-ignore lint/suspicious/noExplicitAny: dynamic value const linhas = ref<Array<LinhaFiltro>>([])
const linhas = ref<Array<LinhaFiltro<any>>>([])
const colunaParaAdicionar = ref<string>("") const chaveParaAdicionar = ref<string>("")
const colunasDisponiveis = computed(() => const chavesDisponiveis = computed(() =>
(props.filtrosBase ?? []).map((b) => String(b.coluna)), (props.filtrosBase ?? []).map((b) => String(b.chave)),
) )
const opcoesParaAdicionar = computed(() => { const opcoesParaAdicionar = computed(() => {
const usadas = new Set(linhas.value.map((l) => String(l.coluna))) const usadas = new Set(linhas.value.map((l) => String(l.chave)))
return (props.filtrosBase ?? []).filter( return (props.filtrosBase ?? []).filter(
(b) => !usadas.has(String(b.coluna)), (b) => !usadas.has(String(b.chave)),
) )
}) })
@ -169,42 +160,30 @@ export default defineComponent({
function normalizarModelo() { function normalizarModelo() {
const base = props.filtrosBase ?? [] const base = props.filtrosBase ?? []
const modelo = Array.isArray(props.modelo) ? props.modelo : [] const modelo = Array.isArray(props.modelo) ? props.modelo : []
// biome-ignore lint/suspicious/noExplicitAny: dynamic model linhas.value = modelo
linhas.value = modelo.map((m: any) => { // biome-ignore lint/suspicious/noExplicitAny: dynamic model item
// operador vem travado no base .map((m: any) => {
const baseItem = const baseItem =
base.find((b) => String(b.coluna) === String(m.coluna)) ?? base[0] base.find((b) => String(b.chave) === String(m.chave)) ?? base[0]
const entrada = (baseItem?.entrada ?? m.entrada) as ComponenteEntrada
// biome-ignore lint/suspicious/noExplicitAny: dynamic column if (!baseItem) return null
const col = (baseItem?.coluna ?? m.coluna) as any
const op = String(baseItem?.operador ?? "=") const entrada = (baseItem.entrada ?? m.entrada) as ComponenteEntrada
const chave = String(baseItem.chave ?? m.chave)
const val = m.valor ?? valorInicialPorEntrada(entrada) const val = m.valor ?? valorInicialPorEntrada(entrada)
return { return {
coluna: col, chave: chave,
operador: op,
entrada, entrada,
valor: val, valor: val,
// biome-ignore lint/suspicious/noExplicitAny: dynamic cast } as LinhaFiltro
} as LinhaFiltro<any>
}) })
.filter(Boolean) as LinhaFiltro[]
// se vazio e existe base, adiciona 1 linha default // se algum filtro mudou a chave para valor inválido, ajusta
// não auto-adiciona; usuário escolhe quais filtros quer usar linhas.value = linhas.value.filter((l) =>
chavesDisponiveis.value.includes(String(l.chave)),
// se algum filtro mudou a coluna para valor inválido, ajusta
for (const l of linhas.value) {
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])) {
// biome-ignore lint/suspicious/noExplicitAny: dynamic sanity
l.entrada = ["texto", { rotulo: "Valor" }] as any
}
}
} }
watch( watch(
@ -216,24 +195,21 @@ export default defineComponent({
) )
function adicionar() { function adicionar() {
if (!colunaParaAdicionar.value) return if (!chaveParaAdicionar.value) return
const b0 = (props.filtrosBase ?? []).find( const b0 = (props.filtrosBase ?? []).find(
(b) => String(b.coluna) === String(colunaParaAdicionar.value), (b) => String(b.chave) === String(chaveParaAdicionar.value),
) )
if (!b0) return if (!b0) return
// evita repetição // evita repetição
if (linhas.value.some((l) => String(l.coluna) === String(b0.coluna))) if (linhas.value.some((l) => String(l.chave) === String(b0.chave))) return
return
linhas.value.push({ linhas.value.push({
// biome-ignore lint/suspicious/noExplicitAny: dynamic column chave: String(b0.chave),
coluna: b0.coluna as any,
entrada: b0.entrada, entrada: b0.entrada,
operador: String(b0.operador ?? "="),
valor: valorInicialPorEntrada(b0.entrada), valor: valorInicialPorEntrada(b0.entrada),
}) })
colunaParaAdicionar.value = "" chaveParaAdicionar.value = ""
} }
function remover(idx: number) { function remover(idx: number) {
@ -252,7 +228,7 @@ export default defineComponent({
emit( emit(
"salvar", "salvar",
linhas.value.map((l) => ({ linhas.value.map((l) => ({
coluna: l.coluna, chave: l.chave,
valor: l.valor, valor: l.valor,
})), })),
) )
@ -261,12 +237,11 @@ export default defineComponent({
return { return {
linhas, linhas,
opcoesParaAdicionar, opcoesParaAdicionar,
colunaParaAdicionar, chaveParaAdicionar,
componenteEntrada, componenteEntrada,
opcoesEntrada, opcoesEntrada,
adicionar, adicionar,
remover, remover,
// exibimos operador fixo só como texto
emitFechar, emitFechar,
emitSalvar, emitSalvar,
emitLimpar, emitLimpar,

View file

@ -16,15 +16,12 @@ function normalizarConfig(valor: unknown): EliTabelaColunasConfig {
return { visiveis: [], invisiveis: [] } return { visiveis: [], invisiveis: [] }
} }
// biome-ignore lint/suspicious/noExplicitAny: dynamic config const v = valor as EliTabelaColunasConfig
const v = valor as any
const visiveis = Array.isArray(v.visiveis) const visiveis = Array.isArray(v.visiveis)
? // biome-ignore lint/suspicious/noExplicitAny: dynamic array item ? v.visiveis.filter((x: unknown) => typeof x === "string")
v.visiveis.filter((x: any) => typeof x === "string")
: [] : []
const invisiveis = Array.isArray(v.invisiveis) const invisiveis = Array.isArray(v.invisiveis)
? // biome-ignore lint/suspicious/noExplicitAny: dynamic array item ? v.invisiveis.filter((x: unknown) => typeof x === "string")
v.invisiveis.filter((x: any) => typeof x === "string")
: [] : []
return { visiveis, invisiveis } return { visiveis, invisiveis }
} }

View file

@ -1,7 +1,7 @@
export type EliTabelaFiltroAvancadoSalvo<T> = Array<{ export type EliTabelaFiltroAvancadoSalvo<T> = Array<{
coluna: keyof T coluna: keyof T
// biome-ignore lint/suspicious/noExplicitAny: dynamic value
valor: any valor: T[keyof T]
}> }>
function key(nomeTabela: string) { function key(nomeTabela: string) {
@ -17,8 +17,7 @@ export function carregarFiltroAvancado<T>(
const parsed = JSON.parse(raw) const parsed = JSON.parse(raw)
return Array.isArray(parsed) return Array.isArray(parsed)
? (parsed as EliTabelaFiltroAvancadoSalvo<T>) ? (parsed as EliTabelaFiltroAvancadoSalvo<T>)
: // biome-ignore lint/suspicious/noExplicitAny: dynamic cast : ([] as unknown as EliTabelaFiltroAvancadoSalvo<T>)
([] as any)
} catch { } catch {
return [] as unknown as EliTabelaFiltroAvancadoSalvo<T> return [] as unknown as EliTabelaFiltroAvancadoSalvo<T>
} }

View file

@ -1,5 +1,8 @@
import type { LucideIcon } from "lucide-vue-next" import type { LucideIcon } from "lucide-vue-next"
import type { operadores, zFiltro } from "p-comuns" import type { tipoFiltro26 } from "p-comuns"
export type tipoFiltro<T> = tipoFiltro26<T>
import type { tipoResposta } from "p-respostas" import type { tipoResposta } from "p-respostas"
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas" import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
import type { import type {
@ -7,9 +10,6 @@ import type {
tipoTabelaCelula, tipoTabelaCelula,
} from "./celulas/tiposTabelaCelulas" } from "./celulas/tiposTabelaCelulas"
// `p-comuns` expõe `zFiltro` (schema). Inferimos o tipo a partir do `parse`.
export type tipoFiltro = ReturnType<(typeof zFiltro)["parse"]>
export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> = readonly [ export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> = readonly [
T, T,
tiposTabelaCelulas[T], tiposTabelaCelulas[T],
@ -72,7 +72,7 @@ export type tipoEliTabelaAcao<T> = {
} }
export type parametrosConsulta<T> = { export type parametrosConsulta<T> = {
filtros?: tipoFiltro[] filtros?: tipoFiltro26<T>[]
coluna_ordem?: keyof T coluna_ordem?: keyof T
direcao_ordem?: "asc" | "desc" direcao_ordem?: "asc" | "desc"
offSet?: number offSet?: number
@ -143,8 +143,13 @@ export type tipoEliTabelaConsulta<T> = {
}[] }[]
filtroAvancado?: { filtroAvancado?: {
coluna: keyof T /** Identificador único do filtro (usado para persistência) */
operador: operadores | keyof typeof operadores chave: string
/** Rótulo exibido no select de adicionar filtro */
rotulo: string
/** vai gerar filtro e mescla com os filtros já existentes */
filtro: (valor: unknown) => tipoFiltro26<T>
/** componente de entrada que vai receber o valor para o filtro */
entrada: ComponenteEntrada entrada: ComponenteEntrada
}[] }[]
} }

View file

@ -18,10 +18,10 @@ export { EliBotao }
export { EliBadge } export { EliBadge }
export { EliCartao } export { EliCartao }
export { criarFiltro26 } from "p-comuns"
export * from "./componentes/EliEntrada" export * from "./componentes/EliEntrada"
// Exportar tudo (componentes + types + helpers) de Tabela e Entrada // Exportar tudo (componentes + types + helpers) de Tabela e Entrada
export * from "./componentes/EliTabela" export * from "./componentes/EliTabela"
// Exportar tipos compartilhados (ex: CartaoStatus) // Exportar tipos compartilhados (ex: CartaoStatus)
export * from "./tipos" export * from "./tipos"

View file

@ -283,8 +283,11 @@ const _filtrarPorBusca = (lista: Linha[], texto?: string) => {
) )
} }
// biome-ignore lint/suspicious/noExplicitAny: playground const _comparar = (
const _comparar = (op: string, valLine: any, valFiltro: any): boolean => { op: string,
valLine: unknown,
valFiltro: unknown,
): boolean => {
switch (op) { switch (op) {
case "=": case "=":
return valLine == valFiltro return valLine == valFiltro
@ -318,13 +321,30 @@ const _comparar = (op: string, valLine: any, valFiltro: any): boolean => {
} }
} }
const _filtrarAvancado = (lista: Linha[], filtros?: tipoFiltro[]) => { const _filtrarAvancado = (lista: Linha[], filtros?: tipoFiltro<Linha>[]) => {
if (!filtros?.length) return [...lista] if (!filtros?.length) return [...lista]
// biome-ignore lint/suspicious/noExplicitAny: playground
return lista.filter((linha: any) => return lista.filter((linha) =>
filtros.every((f) => filtros.every((f) => {
_comparar(String(f.operador), linha?.[String(f.coluna)], f.valor), // f é um objeto { [coluna]: { [op]: valor } }
), // Percorremos as colunas (chaves)
return Object.entries(f).every(([coluna, condicoes]) => {
// Ignorar chaves de lógica complexa por enquanto no playground
if (coluna === "AND" || coluna === "OR") return true
const valLinha = linha[coluna as keyof Linha]
// Se condicoes for objeto { op: val }
if (typeof condicoes === "object" && condicoes !== null) {
return Object.entries(condicoes).every(([op, val]) =>
_comparar(op, valLinha, val),
)
}
// Se for valor direto (igualdade implícita? ou erro?)
// Vamos assumir igualdade se não for objeto
return _comparar("=", valLinha, condicoes)
})
}),
) )
} }
@ -347,8 +367,14 @@ const delay = (ms: number) =>
// -- API PÚBLICA SIMULADA -- // -- API PÚBLICA SIMULADA --
// biome-ignore lint/suspicious/noExplicitAny: playground export async function api_ler(params: {
export async function api_ler(params: any) { texto_busca?: string
filtros?: tipoFiltro<Linha>[]
coluna_ordem?: keyof Linha
direcao_ordem?: "asc" | "desc"
limit?: number
offSet?: number
}) {
await delay(600) // simula latência de rede await delay(600) // simula latência de rede
// 1. Filtro // 1. Filtro

View file

@ -63,6 +63,7 @@
<script lang="ts"> <script lang="ts">
import { BadgeCheck, Eye, Pencil, Plus, Trash2 } from "lucide-vue-next" import { BadgeCheck, Eye, Pencil, Plus, Trash2 } from "lucide-vue-next"
import { criarFiltro26, type tipoFiltro26 } from "p-comuns"
import { codigosResposta } from "p-respostas" import { codigosResposta } from "p-respostas"
import { defineComponent, ref } from "vue" import { defineComponent, ref } from "vue"
import type { ComponenteEntrada } from "@/componentes/EliEntrada/tiposEntradas" import type { ComponenteEntrada } from "@/componentes/EliEntrada/tiposEntradas"
@ -297,21 +298,36 @@ export default defineComponent({
acoesLinha: acoesLinha, acoesLinha: acoesLinha,
filtroAvancado: [ filtroAvancado: [
{ {
coluna: "empreendedor", chave: "empreendedor",
operador: "like", rotulo: "Empreendedor",
filtro: (valor: unknown) =>
criarFiltro26({
// biome-ignore lint/suspicious/noExplicitAny: playground
empreendedor: { like: valor as string } as any,
}) as tipoFiltro26<Linha>,
entrada: ["texto", { rotulo: "Empreendedor" }] as ComponenteEntrada, entrada: ["texto", { rotulo: "Empreendedor" }] as ComponenteEntrada,
}, },
{ {
coluna: "documento", chave: "documento",
operador: "like", rotulo: "Documento",
filtro: (valor: unknown) =>
criarFiltro26({
// biome-ignore lint/suspicious/noExplicitAny: playground
documento: { like: valor as string } as any,
}) as tipoFiltro26<Linha>,
entrada: [ entrada: [
"texto", "texto",
{ rotulo: "Documento", formato: "cpfCnpj" }, { rotulo: "Documento", formato: "cpfCnpj" },
] as ComponenteEntrada, ] as ComponenteEntrada,
}, },
{ {
coluna: "email", chave: "email",
operador: "like", rotulo: "E-mail",
filtro: (valor: unknown) =>
criarFiltro26({
// biome-ignore lint/suspicious/noExplicitAny: playground
email: { like: valor as string } as any,
}) as tipoFiltro26<Linha>,
entrada: [ entrada: [
"texto", "texto",
{ rotulo: "E-mail", formato: "email" }, { rotulo: "E-mail", formato: "email" },