This commit is contained in:
Luiz Silva 2026-02-12 16:38:17 -03:00
parent 63d943d0df
commit f396203085
22 changed files with 1476 additions and 1357 deletions

289
IA.md
View file

@ -15,6 +15,7 @@ Biblioteca (Design System) de componentes para **Vue 3** com **TypeScript** e in
- O `eli-vue` **não cria** nem configura Vuetify no seu projeto.
- `vue` e `vuetify` são **peerDependencies**: o projeto consumidor precisa ter ambos instalados.
- O pacote utiliza internamente **lucide-vue-next** para ícones.
---
@ -29,9 +30,11 @@ pnpm add eli-vue
Se o projeto ainda não tiver as peer dependencies, instale também:
```bash
pnpm add vue vuetify
pnpm add vue vuetify lucide-vue-next
```
> Nota: `lucide-vue-next` é recomendado para passar ícones compatíveis aos componentes (como `EliTabela`).
---
## Uso recomendado
@ -82,6 +85,28 @@ import "eli-vue/dist/eli-vue.css";
---
## Tipagem e Helpers
O pacote exporta tipos e funções utilitárias essenciais para o desenvolvimento com TypeScript.
```ts
import {
// Componentes
EliTabela,
// Helpers
celulaTabela,
// Tipos
EliTabelaConsulta,
EliColuna,
EliTabelaAcao,
CartaoStatus // Tipos compartilhados
} from "eli-vue";
```
---
## Exemplos mínimos
### Botão
@ -214,6 +239,7 @@ import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
// Exemplo de valor ISO vindo do backend/banco
const dataHora = ref<string | null>("2026-01-09T16:15:00Z");
const data = ref<string | null>("2026-01-09T00:00:00-03:00");
return { dataHora, data };
@ -224,71 +250,173 @@ export default defineComponent({
---
## EliTabela (com filtro avançado)
## Outros Componentes
O componente `EliTabela` suporta:
- ordenação
- paginação
- caixa de busca
- **filtro avançado (modal)**
### EliBadge (Indicador)
### Contrato da tabela (resumo)
Badge aprimorado que suporta raios predefinidos ("suave" | "pill").
O tipo principal é `EliTabelaConsulta<T>` (genérico), exportado de `eli-vue`.
O `filtroAvancado` é uma lista de filtros pré-definidos (o usuário só escolhe quais usar e informa valores):
```ts
filtroAvancado?: {
rotulo: string
coluna: keyof T
operador: string // ex.: "like", "=", ">", "in", "isNull"
entrada: ["texto" | "numero" | "dataHora", { rotulo: string; ... }]
}[]
```vue
<template>
<EliBadge :badge="3" color="error" radius="pill">
<v-icon>mdi-bell</v-icon>
</EliBadge>
</template>
```
### Exemplo mínimo
### EliCartao
```ts
import { EliTabela, celulaTabela } from "eli-vue";
import type { EliTabelaConsulta } from "eli-vue";
import type { ComponenteEntrada } from "eli-vue/dist/types/componentes/EliEntrada/tiposEntradas";
Cartão padronizado para representar itens de domínio (ex: propostas) com status coloridos automaticamente.
type Linha = { nome: string; documento: string; email: string };
> **Importante**: O status deve ser um dos valores do tipo `CartaoStatus` ("novo" | "rascunho" | "vendido" | "cancelado").
const tabela: EliTabelaConsulta<Linha> = {
nome: "Exemplo",
mostrarCaixaDeBusca: true,
registros_por_consulta: 10,
colunas: [
{ rotulo: "Nome", celula: (l) => celulaTabela("textoSimples", { texto: l.nome }), visivel: true },
],
filtroAvancado: [
{
rotulo: "Documento",
coluna: "documento",
operador: "like",
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as unknown as ComponenteEntrada,
},
],
consulta: async () => ({
cod: 0,
eCerto: true,
eErro: false,
mensagem: undefined,
valor: { quantidade: 0, valores: [] },
}),
};
```vue
<template>
<EliCartao
titulo="Proposta #123"
status="novo"
variant="outlined"
>
<!-- Conteúdo principal (default slot) -->
<div class="text-body-2">Cliente: ACME Corp</div>
<div class="text-caption text-medium-emphasis">Criado há 2 dias</div>
<!-- Ações (slot 'acoes') -->
<template #acoes>
<EliBotao variant="text" size="small">Ver detalhes</EliBotao>
</template>
</EliCartao>
</template>
<script lang="ts">
import { defineComponent } from "vue";
// O tipo CartaoStatus é exportado para ajudar na tipagem
import type { CartaoStatus } from "eli-vue";
export default defineComponent({
setup() {
const statusAtual: CartaoStatus = "novo";
return { statusAtual };
}
});
</script>
```
---
## Células da EliTabela (celulaTabela)
## EliTabela (Tabela Avançada)
O `eli-vue` expõe o helper `celulaTabela(tipo, dados)` para construir células tipadas.
O componente `EliTabela` suporta ordenação, paginação, busca e **filtro avançado**.
Para type-safety, recomenda-se definir a estrutura da consulta usando `EliTabelaConsulta<T>`.
Tipos disponíveis atualmente:
### Exemplo Completo Tipado
```ts
import { defineComponent } from "vue";
import { EliTabela, celulaTabela } from "eli-vue";
import type { EliTabelaConsulta } from "eli-vue";
import type { ComponenteEntrada } from "eli-vue/dist/types/componentes/EliEntrada/tiposEntradas";
// ^ Nota: Tipos internos podem precisar de caminho completo se não exportados na raiz.
// Melhor prática: use 'any' ou 'unknown' castado se o tipo não estiver disponível,
// mas `EliTabelaConsulta` geralmente infere.
import { BadgeCheck, Pencil } from "lucide-vue-next";
// 1. Defina o tipo da linha
type Usuario = {
id: number;
nome: string;
documento: string;
email: string;
ativo: boolean;
criado_em: string;
};
// 2. Defina a configuração da tabela
const tabelaUsuarios: EliTabelaConsulta<Usuario> = {
nome: "Usuarios",
mostrarCaixaDeBusca: true,
registros_por_consulta: 10,
// Colunas
colunas: [
{
rotulo: "Nome",
celula: (row) => celulaTabela("textoSimples", { texto: row.nome }),
visivel: true,
coluna_ordem: "nome" // Habilita ordenação
},
{
rotulo: "Status",
visivel: true,
celula: (row) => celulaTabela("tags", {
opcoes: [
row.ativo
? { rotulo: "Ativo", cor: "success", icone: BadgeCheck }
: { rotulo: "Inativo", cor: "error" }
]
})
},
{
rotulo: "Criado em",
visivel: true,
celula: (row) => celulaTabela("data", {
valor: row.criado_em,
formato: "data_hora"
})
}
],
// Ações de linha (botões à direita)
acoesLinha: [
{
rotulo: "Editar",
icone: Pencil,
cor: "primary",
acao: (row) => console.log("Editar", row.id)
}
],
// Filtro Avançado (definição dos campos de filtro)
filtroAvancado: [
{
rotulo: "Documento",
coluna: "documento",
operador: "like", // like, =, >, <, etc
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as any,
},
{
rotulo: "Criado em (Início)",
coluna: "criado_em",
operador: ">=",
entrada: ["dataHora", { rotulo: "A partir de", modo: "data" }] as any
}
],
// Função de consulta (simulada ou API real)
consulta: async (params) => {
console.log("Consultando com:", params);
// params contém: { filtro, coluna_ordem, direcao_ordem, offSet, limit, texto_busca }
return {
cod: 0,
eCerto: true,
eErro: false,
mensagem: undefined,
valor: {
quantidade: 1,
valores: [
{ id: 1, nome: "Luiz", documento: "123", email: "a@a.com", ativo: true, criado_em: "2024-01-01" }
]
},
};
},
};
```
### Tipos de Células (`celulaTabela`)
Helper: `celulaTabela(tipo, dados)`
- `textoSimples`: `{ texto: string; acao?: () => void }`
- `textoTruncado`: `{ texto: string; acao?: () => void }`
@ -296,35 +424,6 @@ Tipos disponíveis atualmente:
- `tags`: `{ opcoes: { rotulo: string; cor?: string; icone?: LucideIcon; acao?: () => void }[] }`
- `data`: `{ valor: string; formato: "data" | "data_hora" | "relativo"; acao?: () => void }`
### Exemplo: célula `tags`
```ts
import { celulaTabela } from "eli-vue";
import { BadgeCheck, Pencil } from "lucide-vue-next";
celula: (l) =>
celulaTabela("tags", {
opcoes: [
{ rotulo: "Ativo", cor: "success", icone: BadgeCheck },
{ rotulo: "Editar", cor: "primary", icone: Pencil, acao: () => editar(l) },
],
})
```
### Exemplo: célula `data`
```ts
import { celulaTabela } from "eli-vue";
celula: (l) =>
celulaTabela("data", {
valor: l.criado_em, // ISO
formato: "data", // "data" | "data_hora" | "relativo"
})
```
> Observação: em modo simulação/local, a tabela pode buscar uma lista completa e aplicar filtro/paginação localmente.
---
## Troubleshooting (para IAs)
@ -332,20 +431,24 @@ celula: (l) =>
### 1) “Failed to resolve component”
Provável causa: componente não foi registrado.
- Se estiver usando plugin: confirme `app.use(EliVue)`
- Se estiver importando direto: registre localmente no componente
### 2) Estilos não aplicam
Confirme que o projeto importou:
- `vuetify/styles`
- `eli-vue/dist/eli-vue.css`
### 3) Ícones não aparecem
### 3) Erro de tipo em `celulaTabela`
Alguns exemplos usam `<v-icon>` (MDI). O `eli-vue` **não instala** ícones automaticamente no projeto consumidor.
Verifique se você importou `celulaTabela` de `eli-vue`.
Certifique-se de que o segundo argumento corresponde ao tipo da célula (ex: `textoSimples` pede `{ texto: string }`).
### 4) Ícones não aparecem
O `eli-vue` espera ícones do pacote `lucide-vue-next` (ou compatíveis com Component type do Vue).
Certifique-se de passar o componente do ícone (ex: `Pencil`), e não string.
---
@ -353,19 +456,17 @@ Alguns exemplos usam `<v-icon>` (MDI). O `eli-vue` **não instala** ícones auto
Quando for integrar `eli-vue` num projeto existente:
1) Verifique se **Vue 3** e **Vuetify 3** já estão configurados.
2) Prefira usar o **plugin** do `eli-vue` (simplifica registro).
1) Verifique se **Vue 3**, **Vuetify 3** e **lucide-vue-next** estão instalados.
2) Prefira usar o **plugin** do `eli-vue` (simplifica registro global).
3) Garanta o import do CSS do pacote (`eli-vue/dist/eli-vue.css`).
4) Ao ver erros de tipos, valide se o projeto está usando TypeScript e se o build está resolvendo `types` corretamente.
4) Use `celulaTabela` para construir colunas de tabelas de forma tipada.
5) Ao definir tabelas, use `EliTabelaConsulta<T>` para garantir que colunas e filtros batam com o tipo de dados.
---
## Quando este arquivo deve ser atualizado
Atualize este `IA.md` quando houver mudanças em qualquer um destes pontos:
- caminho/nome do CSS em `dist/`
- forma de instalação e/ou peerDependencies
- forma de uso do plugin/export principal
- lista de exports públicos (novos componentes, renomes, remoções)
- mudanças de comportamento relevantes para consumo
Atualize este `IA.md` quando houver mudanças em:
- Estrutura de exports (novos componentes ou helpers).
- Tipagem das células ou entradas.
- Dependências obrigatórias.

2
dist/eli-vue.css vendored

File diff suppressed because one or more lines are too long

2014
dist/eli-vue.es.js vendored

File diff suppressed because it is too large Load diff

26
dist/eli-vue.umd.js vendored

File diff suppressed because one or more lines are too long

View file

@ -27,8 +27,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[];
dica?: string;
dicaPersistente?: boolean;
densidade?: import("../../tipos").CampoDensidade;
variante?: import("../../tipos").CampoVariante;
densidade?: import("../..").CampoDensidade;
variante?: import("../..").CampoVariante;
};
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
"update:value": (_v: EntradaParagrafo["value"]) => true;

View file

@ -36,8 +36,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[];
dica?: string;
dicaPersistente?: boolean;
densidade?: import("../../tipos").CampoDensidade;
variante?: import("../../tipos").CampoVariante;
densidade?: import("../..").CampoDensidade;
variante?: import("../..").CampoVariante;
};
itens: import("vue").Ref<{
chave: string;

View file

@ -127,8 +127,8 @@ export declare const registryTabelaCelulas: {
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
required: false;
default: undefined;
@ -174,11 +174,11 @@ export declare const registryTabelaCelulas: {
default: undefined;
};
densidade: {
type: import("vue").PropType<import("../../tipos/entrada.js").CampoDensidade>;
type: import("vue").PropType<import("../../index.js").CampoDensidade>;
default: undefined;
};
variante: {
type: import("vue").PropType<import("../../tipos/entrada.js").CampoVariante>;
type: import("vue").PropType<import("../../index.js").CampoVariante>;
default: undefined;
};
min: {
@ -209,8 +209,8 @@ export declare const registryTabelaCelulas: {
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
desabilitadoEfetivo: import("vue").ComputedRef<boolean>;
emitCompatFocus: () => void;
@ -243,8 +243,8 @@ export declare const registryTabelaCelulas: {
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
required: false;
default: undefined;
@ -290,11 +290,11 @@ export declare const registryTabelaCelulas: {
default: undefined;
};
densidade: {
type: import("vue").PropType<import("../../tipos/entrada.js").CampoDensidade>;
type: import("vue").PropType<import("../../index.js").CampoDensidade>;
default: undefined;
};
variante: {
type: import("vue").PropType<import("../../tipos/entrada.js").CampoVariante>;
type: import("vue").PropType<import("../../index.js").CampoVariante>;
default: undefined;
};
min: {
@ -324,8 +324,8 @@ export declare const registryTabelaCelulas: {
dicaPersistente: boolean;
min: string | undefined;
max: string | undefined;
densidade: import("../../tipos/entrada.js").CampoDensidade;
variante: import("../../tipos/entrada.js").CampoVariante;
densidade: import("../../index.js").CampoDensidade;
variante: import("../../index.js").CampoVariante;
opcoes: {
rotulo: string;
placeholder?: string;
@ -338,8 +338,8 @@ export declare const registryTabelaCelulas: {
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
};
value: string | null | undefined;
placeholder: string;
@ -364,8 +364,8 @@ export declare const registryTabelaCelulas: {
mensagensErro?: string | string[];
dica?: string;
dicaPersistente?: boolean;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
required: true;
};
@ -386,8 +386,8 @@ export declare const registryTabelaCelulas: {
mensagensErro?: string | string[];
dica?: string;
dicaPersistente?: boolean;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
};
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
"update:value": (_v: string | null | undefined) => true;
@ -412,8 +412,8 @@ export declare const registryTabelaCelulas: {
mensagensErro?: string | string[];
dica?: string;
dicaPersistente?: boolean;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
required: true;
};
@ -2032,8 +2032,8 @@ export declare const registryTabelaCelulas: {
mensagensErro?: string | string[];
dica?: string;
dicaPersistente?: boolean;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
required: true;
};
@ -2059,8 +2059,8 @@ export declare const registryTabelaCelulas: {
mensagensErro?: string | string[];
dica?: string;
dicaPersistente?: boolean;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
};
itens: import("vue").Ref<{
chave: string;
@ -2101,8 +2101,8 @@ export declare const registryTabelaCelulas: {
mensagensErro?: string | string[];
dica?: string;
dicaPersistente?: boolean;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
required: true;
};

View file

@ -21,6 +21,12 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
erro: import("vue").Ref<string | null, string | null>;
linhas: import("vue").Ref<unknown[], unknown[]>;
linhasPaginadas: import("vue").ComputedRef<unknown[]>;
filtrosAvancadosAtivos: import("vue").ComputedRef<{
coluna: string;
valor: any;
operador: "in" | "=" | "!=" | ">" | ">=" | "<" | "<=" | "like" | "isNull";
ou?: boolean | undefined;
}[]>;
quantidadeFiltrada: import("vue").ComputedRef<number>;
quantidade: import("vue").Ref<number, number>;
menuAberto: import("vue").Ref<number | null, number | null>;
@ -29,6 +35,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
colunaOrdenacao: import("vue").Ref<string | null, string | null>;
direcaoOrdenacao: import("vue").Ref<"desc" | "asc", "desc" | "asc">;
totalPaginas: import("vue").ComputedRef<number>;
registrosPorConsulta: import("vue").ComputedRef<number>;
exibirBusca: import("vue").ComputedRef<boolean>;
exibirFiltroAvancado: import("vue").ComputedRef<boolean>;
acoesCabecalho: import("vue").ComputedRef<{
@ -36,6 +43,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
cor?: string;
rotulo: string;
acao: () => void;
atualizarConsulta?: () => Promise<void>;
editarLista?: ((lista: any[]) => Promise<any[]>) | undefined;
}[]>;
temAcoesCabecalho: import("vue").ComputedRef<boolean>;
temAcoes: import("vue").ComputedRef<boolean>;
@ -764,8 +773,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean | undefined;
min?: string | undefined;
max?: string | undefined;
densidade?: import("../../tipos/entrada.js").CampoDensidade | undefined;
variante?: import("../../tipos/entrada.js").CampoVariante | undefined;
densidade?: import("../../index.js").CampoDensidade | undefined;
variante?: import("../../index.js").CampoVariante | undefined;
}] | readonly ["numero", {
rotulo: string;
placeholder?: string | undefined;
@ -782,8 +791,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[] | undefined;
dica?: string | undefined;
dicaPersistente?: boolean | undefined;
densidade?: import("../../tipos/entrada.js").CampoDensidade | undefined;
variante?: import("../../tipos/entrada.js").CampoVariante | undefined;
densidade?: import("../../index.js").CampoDensidade | undefined;
variante?: import("../../index.js").CampoVariante | undefined;
}] | readonly ["selecao", {
rotulo: string;
placeholder?: string | undefined;
@ -799,8 +808,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[] | undefined;
dica?: string | undefined;
dicaPersistente?: boolean | undefined;
densidade?: import("../../tipos/entrada.js").CampoDensidade | undefined;
variante?: import("../../tipos/entrada.js").CampoVariante | undefined;
densidade?: import("../../index.js").CampoDensidade | undefined;
variante?: import("../../index.js").CampoVariante | undefined;
}];
operador: string;
valor: any;
@ -827,8 +836,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean | undefined;
min?: string | undefined;
max?: string | undefined;
densidade?: import("../../tipos/entrada.js").CampoDensidade | undefined;
variante?: import("../../tipos/entrada.js").CampoVariante | undefined;
densidade?: import("../../index.js").CampoDensidade | undefined;
variante?: import("../../index.js").CampoVariante | undefined;
}] | readonly ["numero", {
rotulo: string;
placeholder?: string | undefined;
@ -845,8 +854,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[] | undefined;
dica?: string | undefined;
dicaPersistente?: boolean | undefined;
densidade?: import("../../tipos/entrada.js").CampoDensidade | undefined;
variante?: import("../../tipos/entrada.js").CampoVariante | undefined;
densidade?: import("../../index.js").CampoDensidade | undefined;
variante?: import("../../index.js").CampoVariante | undefined;
}] | readonly ["selecao", {
rotulo: string;
placeholder?: string | undefined;
@ -862,8 +871,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[] | undefined;
dica?: string | undefined;
dicaPersistente?: boolean | undefined;
densidade?: import("../../tipos/entrada.js").CampoDensidade | undefined;
variante?: import("../../tipos/entrada.js").CampoVariante | undefined;
densidade?: import("../../index.js").CampoDensidade | undefined;
variante?: import("../../index.js").CampoVariante | undefined;
}];
operador: string;
valor: any;
@ -1000,8 +1009,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
required: false;
default: undefined;
@ -1047,11 +1056,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: undefined;
};
densidade: {
type: PropType<import("../../tipos/entrada.js").CampoDensidade>;
type: PropType<import("../../index.js").CampoDensidade>;
default: undefined;
};
variante: {
type: PropType<import("../../tipos/entrada.js").CampoVariante>;
type: PropType<import("../../index.js").CampoVariante>;
default: undefined;
};
min: {
@ -1082,8 +1091,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
desabilitadoEfetivo: import("vue").ComputedRef<boolean>;
emitCompatFocus: () => void;
@ -1116,8 +1125,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
}>;
required: false;
default: undefined;
@ -1163,11 +1172,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: undefined;
};
densidade: {
type: PropType<import("../../tipos/entrada.js").CampoDensidade>;
type: PropType<import("../../index.js").CampoDensidade>;
default: undefined;
};
variante: {
type: PropType<import("../../tipos/entrada.js").CampoVariante>;
type: PropType<import("../../index.js").CampoVariante>;
default: undefined;
};
min: {
@ -1197,8 +1206,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente: boolean;
min: string | undefined;
max: string | undefined;
densidade: import("../../tipos/entrada.js").CampoDensidade;
variante: import("../../tipos/entrada.js").CampoVariante;
densidade: import("../../index.js").CampoDensidade;
variante: import("../../index.js").CampoVariante;
opcoes: {
rotulo: string;
placeholder?: string;
@ -1211,8 +1220,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos/entrada.js").CampoDensidade;
variante?: import("../../tipos/entrada.js").CampoVariante;
densidade?: import("../../index.js").CampoDensidade;
variante?: import("../../index.js").CampoVariante;
};
value: string | null | undefined;
placeholder: string;

View file

@ -41,8 +41,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean | undefined;
min?: string | undefined;
max?: string | undefined;
densidade?: import("../../tipos").CampoDensidade | undefined;
variante?: import("../../tipos").CampoVariante | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["numero", {
rotulo: string;
placeholder?: string | undefined;
@ -59,8 +59,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[] | undefined;
dica?: string | undefined;
dicaPersistente?: boolean | undefined;
densidade?: import("../../tipos").CampoDensidade | undefined;
variante?: import("../../tipos").CampoVariante | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["selecao", {
rotulo: string;
placeholder?: string | undefined;
@ -76,8 +76,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[] | undefined;
dica?: string | undefined;
dicaPersistente?: boolean | undefined;
densidade?: import("../../tipos").CampoDensidade | undefined;
variante?: import("../../tipos").CampoVariante | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}];
operador: string;
valor: any;
@ -99,8 +99,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean | undefined;
min?: string | undefined;
max?: string | undefined;
densidade?: import("../../tipos").CampoDensidade | undefined;
variante?: import("../../tipos").CampoVariante | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["numero", {
rotulo: string;
placeholder?: string | undefined;
@ -117,8 +117,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[] | undefined;
dica?: string | undefined;
dicaPersistente?: boolean | undefined;
densidade?: import("../../tipos").CampoDensidade | undefined;
variante?: import("../../tipos").CampoVariante | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["selecao", {
rotulo: string;
placeholder?: string | undefined;
@ -134,8 +134,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
mensagensErro?: string | string[] | undefined;
dica?: string | undefined;
dicaPersistente?: boolean | undefined;
densidade?: import("../../tipos").CampoDensidade | undefined;
variante?: import("../../tipos").CampoVariante | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}];
operador: string;
valor: any;
@ -272,8 +272,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos").CampoDensidade;
variante?: import("../../tipos").CampoVariante;
densidade?: import("../..").CampoDensidade;
variante?: import("../..").CampoVariante;
}>;
required: false;
default: undefined;
@ -319,11 +319,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: undefined;
};
densidade: {
type: PropType<import("../../tipos").CampoDensidade>;
type: PropType<import("../..").CampoDensidade>;
default: undefined;
};
variante: {
type: PropType<import("../../tipos").CampoVariante>;
type: PropType<import("../..").CampoVariante>;
default: undefined;
};
min: {
@ -354,8 +354,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos").CampoDensidade;
variante?: import("../../tipos").CampoVariante;
densidade?: import("../..").CampoDensidade;
variante?: import("../..").CampoVariante;
}>;
desabilitadoEfetivo: import("vue").ComputedRef<boolean>;
emitCompatFocus: () => void;
@ -388,8 +388,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos").CampoDensidade;
variante?: import("../../tipos").CampoVariante;
densidade?: import("../..").CampoDensidade;
variante?: import("../..").CampoVariante;
}>;
required: false;
default: undefined;
@ -435,11 +435,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: undefined;
};
densidade: {
type: PropType<import("../../tipos").CampoDensidade>;
type: PropType<import("../..").CampoDensidade>;
default: undefined;
};
variante: {
type: PropType<import("../../tipos").CampoVariante>;
type: PropType<import("../..").CampoVariante>;
default: undefined;
};
min: {
@ -469,8 +469,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente: boolean;
min: string | undefined;
max: string | undefined;
densidade: import("../../tipos").CampoDensidade;
variante: import("../../tipos").CampoVariante;
densidade: import("../..").CampoDensidade;
variante: import("../..").CampoVariante;
opcoes: {
rotulo: string;
placeholder?: string;
@ -483,8 +483,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean;
min?: string;
max?: string;
densidade?: import("../../tipos").CampoDensidade;
variante?: import("../../tipos").CampoVariante;
densidade?: import("../..").CampoDensidade;
variante?: import("../..").CampoVariante;
};
value: string | null | undefined;
placeholder: string;

View file

@ -96,8 +96,17 @@ 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 */
filtroAvancado?: {
rotulo: string;
coluna: keyof T;

View file

@ -59,15 +59,15 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: string;
};
location: {
type: PropType<import("../../tipos").IndicadorLocalizacao>;
type: PropType<import("../..").IndicadorLocalizacao>;
default: string;
};
offsetX: {
type: PropType<import("../../tipos").IndicadorOffset>;
type: PropType<import("../..").IndicadorOffset>;
default: string;
};
offsetY: {
type: PropType<import("../../tipos").IndicadorOffset>;
type: PropType<import("../..").IndicadorOffset>;
default: string;
};
dot: {
@ -83,7 +83,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: undefined;
};
radius: {
type: PropType<import("../../tipos").IndicadorPresetRaio | import("../../tipos").CssLength>;
type: PropType<import("../..").IndicadorPresetRaio | import("../..").CssLength>;
default: string;
};
}>, {
@ -97,15 +97,15 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: string;
};
location: {
type: PropType<import("../../tipos").IndicadorLocalizacao>;
type: PropType<import("../..").IndicadorLocalizacao>;
default: string;
};
offsetX: {
type: PropType<import("../../tipos").IndicadorOffset>;
type: PropType<import("../..").IndicadorOffset>;
default: string;
};
offsetY: {
type: PropType<import("../../tipos").IndicadorOffset>;
type: PropType<import("../..").IndicadorOffset>;
default: string;
};
dot: {
@ -121,18 +121,18 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
default: undefined;
};
radius: {
type: PropType<import("../../tipos").IndicadorPresetRaio | import("../../tipos").CssLength>;
type: PropType<import("../..").IndicadorPresetRaio | import("../..").CssLength>;
default: string;
};
}>> & Readonly<{}>, {
color: string;
location: import("../../tipos").IndicadorLocalizacao;
offsetX: import("../../tipos").IndicadorOffset;
offsetY: import("../../tipos").IndicadorOffset;
location: import("../..").IndicadorLocalizacao;
offsetX: import("../..").IndicadorOffset;
offsetY: import("../..").IndicadorOffset;
dot: boolean;
visible: boolean;
badge: string | number | undefined;
radius: import("../../tipos").IndicadorPresetRaio | import("../../tipos").CssLength;
radius: import("../..").IndicadorPresetRaio | import("../..").CssLength;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
/**

View file

@ -11,11 +11,11 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
default: string;
};
variant: {
type: import("vue").PropType<import("../../tipos/botao.js").BotaoVariante>;
type: import("vue").PropType<import("../../index.js").BotaoVariante>;
default: string;
};
size: {
type: import("vue").PropType<import("../../tipos/botao.js").BotaoTamanho>;
type: import("vue").PropType<import("../../index.js").BotaoTamanho>;
default: string;
};
disabled: {
@ -32,11 +32,11 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
default: string;
};
variant: {
type: import("vue").PropType<import("../../tipos/botao.js").BotaoVariante>;
type: import("vue").PropType<import("../../index.js").BotaoVariante>;
default: string;
};
size: {
type: import("vue").PropType<import("../../tipos/botao.js").BotaoTamanho>;
type: import("vue").PropType<import("../../index.js").BotaoTamanho>;
default: string;
};
disabled: {
@ -49,8 +49,8 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
};
}>> & Readonly<{}>, {
color: string;
variant: import("../../tipos/botao.js").BotaoVariante;
size: import("../../tipos/botao.js").BotaoTamanho;
variant: import("../../index.js").BotaoVariante;
size: import("../../index.js").BotaoTamanho;
disabled: boolean;
loading: boolean;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@ -60,15 +60,15 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
default: string;
};
location: {
type: import("vue").PropType<import("../../tipos/indicador.js").IndicadorLocalizacao>;
type: import("vue").PropType<import("../../index.js").IndicadorLocalizacao>;
default: string;
};
offsetX: {
type: import("vue").PropType<import("../../tipos/indicador.js").IndicadorOffset>;
type: import("vue").PropType<import("../../index.js").IndicadorOffset>;
default: string;
};
offsetY: {
type: import("vue").PropType<import("../../tipos/indicador.js").IndicadorOffset>;
type: import("vue").PropType<import("../../index.js").IndicadorOffset>;
default: string;
};
dot: {
@ -84,7 +84,7 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
default: undefined;
};
radius: {
type: import("vue").PropType<import("../../tipos/indicador.js").IndicadorPresetRaio | import("../../tipos/indicador.js").CssLength>;
type: import("vue").PropType<import("../../index.js").IndicadorPresetRaio | import("../../index.js").CssLength>;
default: string;
};
}>, {
@ -98,15 +98,15 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
default: string;
};
location: {
type: import("vue").PropType<import("../../tipos/indicador.js").IndicadorLocalizacao>;
type: import("vue").PropType<import("../../index.js").IndicadorLocalizacao>;
default: string;
};
offsetX: {
type: import("vue").PropType<import("../../tipos/indicador.js").IndicadorOffset>;
type: import("vue").PropType<import("../../index.js").IndicadorOffset>;
default: string;
};
offsetY: {
type: import("vue").PropType<import("../../tipos/indicador.js").IndicadorOffset>;
type: import("vue").PropType<import("../../index.js").IndicadorOffset>;
default: string;
};
dot: {
@ -122,18 +122,18 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
default: undefined;
};
radius: {
type: import("vue").PropType<import("../../tipos/indicador.js").IndicadorPresetRaio | import("../../tipos/indicador.js").CssLength>;
type: import("vue").PropType<import("../../index.js").IndicadorPresetRaio | import("../../index.js").CssLength>;
default: string;
};
}>> & Readonly<{}>, {
color: string;
location: import("../../tipos/indicador.js").IndicadorLocalizacao;
offsetX: import("../../tipos/indicador.js").IndicadorOffset;
offsetY: import("../../tipos/indicador.js").IndicadorOffset;
location: import("../../index.js").IndicadorLocalizacao;
offsetX: import("../../index.js").IndicadorOffset;
offsetY: import("../../index.js").IndicadorOffset;
dot: boolean;
visible: boolean;
badge: string | number | undefined;
radius: import("../../tipos/indicador.js").IndicadorPresetRaio | import("../../tipos/indicador.js").CssLength;
radius: import("../../index.js").IndicadorPresetRaio | import("../../index.js").CssLength;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
EliEntradaTexto: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
value: {

View file

@ -4,13 +4,12 @@ import { EliOlaMundo } from "./componentes/ola_mundo";
import { EliBotao } from "./componentes/botao";
import { EliBadge } from "./componentes/indicador";
import { EliCartao } from "./componentes/cartao";
import { EliTabela } from "./componentes/EliTabela";
import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora, EliEntradaParagrafo, EliEntradaSelecao } from "./componentes/EliEntrada";
export { EliOlaMundo };
export { EliBotao };
export { EliBadge };
export { EliCartao };
export { EliTabela };
export { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora, EliEntradaParagrafo, EliEntradaSelecao };
export * from "./componentes/EliTabela";
export * from "./componentes/EliEntrada";
export * from "./tipos";
declare const EliVue: Plugin;
export default EliVue;

View file

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

View file

@ -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,

View file

@ -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>

View file

@ -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) }}

View file

@ -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>

View file

@ -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,

View file

@ -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) {

View file

@ -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,